1use crate::core::error::{Error, Kind, Result};
2use crate::profile::browser::digest;
3use crate::profile::{Browser, ChromiumBrand, Family, Platform};
4
5use super::builder::derive::{
6 DerivedIdentity, IdentityInputs, IdentitySource, derive_identity, resolve_presented,
7};
8use super::{Session, SessionInner};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
11#[serde(try_from = "SavedIdentity", into = "SavedIdentity")]
12pub struct Identity {
13 http: Browser,
14 tls: Browser,
15 platform: Platform,
16 brand: Option<ChromiumBrand>,
17}
18
19#[derive(serde::Serialize, serde::Deserialize)]
20struct SavedIdentity {
21 http: Browser,
22 tls: Browser,
23 platform: Platform,
24 #[serde(default, skip_serializing_if = "Option::is_none")]
25 brand: Option<ChromiumBrand>,
26}
27
28impl From<Identity> for SavedIdentity {
29 fn from(id: Identity) -> Self {
30 Self {
31 http: id.http,
32 tls: id.tls,
33 platform: id.platform,
34 brand: id.brand,
35 }
36 }
37}
38
39impl TryFrom<SavedIdentity> for Identity {
40 type Error = Error;
41
42 fn try_from(saved: SavedIdentity) -> Result<Self> {
43 if saved.http.family() != saved.tls.family() {
44 return Err(Error::new(Kind::Config).with_message(format!(
45 "saved identity tls {} is not the same family as {}",
46 saved.tls, saved.http
47 )));
48 }
49 Ok(Self {
50 http: saved.http,
51 tls: saved.tls,
52 platform: saved.platform,
53 brand: saved.brand,
54 })
55 }
56}
57
58impl Identity {
59 #[must_use]
60 pub fn locked(browser: Browser, platform: Platform) -> Self {
61 Self {
62 http: browser,
63 tls: browser.hello_rep(),
64 platform,
65 brand: None,
66 }
67 }
68
69 #[must_use]
70 pub fn with_brand(self, brand: ChromiumBrand) -> Self {
71 Self {
72 brand: Some(brand),
73 ..self
74 }
75 }
76
77 #[must_use]
78 pub fn brand(self) -> Option<ChromiumBrand> {
79 self.brand
80 }
81
82 pub fn rotate_tls(self, tls: Browser) -> Result<Self> {
83 if self.http.family() != tls.family() {
84 return Err(Error::new(Kind::Config).with_message(format!(
85 "tls rotate {tls} is not the same family as {}",
86 self.http
87 )));
88 }
89 Ok(Self {
90 tls: tls.hello_rep(),
91 ..self
92 })
93 }
94
95 pub fn rotate_hello(self) -> Result<Self> {
96 let mut hellos: Vec<Browser> = Vec::new();
97 for candidate in Browser::all() {
98 if candidate.family() != self.http.family() {
99 continue;
100 }
101 let rep = candidate.hello_rep();
102 if !hellos.contains(&rep) {
103 hellos.push(rep);
104 }
105 }
106 if hellos.len() < 2 {
107 return Err(Error::new(Kind::Config)
108 .with_message(format!("no other hello in family {}", self.http.family())));
109 }
110 let current = self.tls.hello_rep();
111 let index = hellos.iter().position(|&h| h == current).unwrap_or(0);
112 self.rotate_tls(hellos[(index + 1) % hellos.len()])
113 }
114
115 pub fn switch_family(self, dest: Browser) -> Result<Self> {
116 if dest.family() == self.http.family() {
117 return Err(Error::new(Kind::Config).with_message(format!(
118 "switch_family {dest} is the same family as {} — rotate_tls instead",
119 self.http
120 )));
121 }
122 let id = Self::locked(dest, self.platform);
123 drop(id.user_agent()?);
124 Ok(id)
125 }
126
127 #[must_use]
128 pub fn http(self) -> Browser {
129 self.http
130 }
131
132 #[must_use]
133 pub fn tls(self) -> Browser {
134 self.tls
135 }
136
137 #[must_use]
138 pub fn platform(self) -> Platform {
139 self.platform
140 }
141
142 pub(crate) fn user_agent(self) -> Result<String> {
143 resolve_presented(
144 self.http.platform_profile(self.platform),
145 self.platform,
146 ChromiumBrand::Chrome,
147 )
148 .map(|identity| identity.user_agent)
149 }
150}
151
152#[derive(Debug, Clone, PartialEq, Eq)]
153#[non_exhaustive]
154pub struct SessionIdentity {
155 identity: Option<Identity>,
156 platform: Platform,
157 brand: Option<ChromiumBrand>,
158 user_agent: String,
159 saved: Option<Identity>,
160 profile_id: Option<String>,
161 profile_toml: Option<std::sync::Arc<str>>,
162 languages: Option<Vec<String>>,
163}
164
165impl SessionIdentity {
166 #[must_use]
167 pub fn to_identity(&self) -> Option<Identity> {
168 self.saved
169 }
170
171 #[must_use]
172 pub fn profile_id(&self) -> Option<&str> {
173 self.profile_id.as_deref()
174 }
175
176 #[must_use]
177 pub fn identity(&self) -> Option<Identity> {
178 self.identity
179 }
180
181 #[must_use]
182 pub fn export_profile(&self) -> Option<String> {
183 self.profile_toml.as_deref().map(str::to_owned)
184 }
185
186 #[must_use]
187 pub fn languages(&self) -> Option<&[String]> {
188 self.languages.as_deref()
189 }
190
191 #[must_use]
192 pub fn browser(&self) -> Option<Browser> {
193 self.identity.map(Identity::http)
194 }
195
196 #[must_use]
197 pub fn platform(&self) -> Platform {
198 self.platform
199 }
200
201 #[must_use]
202 pub fn brand(&self) -> Option<ChromiumBrand> {
203 self.brand
204 }
205
206 #[must_use]
207 pub fn user_agent(&self) -> &str {
208 &self.user_agent
209 }
210}
211
212impl Session {
213 #[must_use]
214 pub fn identity(&self) -> SessionIdentity {
215 let brand = match self.inner.brand {
216 ChromiumBrand::Chrome => self
217 .inner
218 .browser
219 .filter(|browser| browser.family() == Family::Chrome)
220 .map(|_| ChromiumBrand::Chrome),
221 other => Some(other),
222 };
223 let saved = self.saved_identity(brand);
224 let profile_toml = self
225 .inner
226 .impersonates
227 .then(|| self.inner.profile.source())
228 .flatten()
229 .map(std::sync::Arc::from);
230 let profile_id = match (saved, profile_toml.as_deref()) {
231 (Some(id), _) => Some(profile_id(id, self.inner.brand)),
232 (None, Some(source)) => Some(source_profile_id(
233 source,
234 self.inner.platform,
235 self.inner.brand,
236 )),
237 (None, None) => None,
238 };
239 SessionIdentity {
240 identity: self.inner.identity,
241 platform: self.inner.platform,
242 brand,
243 user_agent: self.inner.user_agent.clone(),
244 saved,
245 profile_id,
246 profile_toml,
247 languages: self.inner.languages.clone(),
248 }
249 }
250
251 fn saved_identity(&self, brand: Option<ChromiumBrand>) -> Option<Identity> {
252 let identity = self.inner.identity?;
253 Some(Identity {
254 tls: self.inner.browser?,
255 brand,
256 ..identity
257 })
258 }
259
260 pub fn with_identity(&self, identity: Identity) -> Result<Self> {
261 if self.inner.browser.is_none() {
262 return Err(Error::new(Kind::Config)
263 .with_message("with_identity requires an impersonating session"));
264 }
265 let mut session = self.clone();
266 let inner = std::sync::Arc::make_mut(&mut session.inner);
267 let brand = identity.brand().unwrap_or(inner.brand);
268 let derived = derive_identity(IdentityInputs {
269 source: IdentitySource::Browser {
270 tls: identity.tls(),
271 http: identity.http(),
272 },
273 platform: identity.platform(),
274 brand,
275 compression: inner.compression,
276 #[cfg(feature = "http3")]
277 h3_required: inner.protocol_policy.requires_h3(),
278 tcp: inner.connector.tcp_profile(),
279 audit: inner.audit_tls.is_some(),
280 default_headers: &inner.default_headers,
281 languages: inner.languages.as_deref(),
282 })?;
283 inner.connector = inner
284 .connector
285 .with_profile(&derived.profile)
286 .map_err(Error::from)?;
287 inner.pool = std::sync::Arc::new(inner.pool.shared_view(profile_hash(identity, brand)));
288 inner.adopt(derived);
289 inner.brand = brand;
290 Ok(session)
291 }
292}
293
294fn profile_id(identity: Identity, brand: ChromiumBrand) -> String {
295 let hash = profile_hash(identity, brand);
296 format!("{hash:016x}")
297}
298
299fn profile_hash(identity: Identity, brand: ChromiumBrand) -> u64 {
300 let http = identity.http.profile_digest().to_le_bytes();
301 let tls = identity.tls.profile_digest().to_le_bytes();
302 digest(&[
303 &http,
304 &tls,
305 identity.platform.id().as_bytes(),
306 brand.id().as_bytes(),
307 ])
308}
309
310fn source_profile_id(source: &str, platform: Platform, brand: ChromiumBrand) -> String {
311 match Browser::matching_source(source) {
312 Some(browser) => profile_id(
313 Identity {
314 http: browser,
315 tls: browser,
316 platform,
317 brand: None,
318 },
319 brand,
320 ),
321 None => format!(
322 "{:016x}",
323 digest(&[
324 source.as_bytes(),
325 platform.id().as_bytes(),
326 brand.id().as_bytes()
327 ])
328 ),
329 }
330}
331
332impl SessionInner {
333 fn adopt(&mut self, derived: DerivedIdentity) {
334 let DerivedIdentity {
335 browser,
336 identity,
337 platform,
338 profile,
339 header_style,
340 header_order,
341 user_agent,
342 sec_ch_ua,
343 accept_language,
344 h2_config,
345 #[cfg(feature = "http3")]
346 h3_config,
347 audit_tls,
348 } = derived;
349 self.browser = browser;
350 self.identity = identity;
351 self.platform = platform;
352 self.profile = profile;
353 self.header_style = header_style;
354 self.header_order = header_order;
355 self.user_agent = user_agent;
356 self.sec_ch_ua = sec_ch_ua;
357 self.accept_language = accept_language;
358 self.h2_config = h2_config;
359 #[cfg(feature = "http3")]
360 {
361 self.h3_config = h3_config;
362 }
363 self.audit_tls = audit_tls;
364 }
365}