leyline/core/session/
identity.rs1use crate::core::error::{Error, Kind, Result};
2use crate::profile::{Browser, ChromiumBrand, Family, Platform};
3
4use super::builder::derive::{
5 DerivedIdentity, IdentityInputs, IdentitySource, derive_identity, resolve_presented,
6};
7use super::{Session, SessionInner};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub struct Identity {
11 http: Browser,
12 tls: Browser,
13 platform: Platform,
14}
15
16impl Identity {
17 #[must_use]
18 pub fn locked(browser: Browser, platform: Platform) -> Self {
19 Self {
20 http: browser,
21 tls: browser.hello_rep(),
22 platform,
23 }
24 }
25
26 pub fn rotate_tls(self, tls: Browser) -> Result<Self> {
27 if self.http.family() != tls.family() {
28 return Err(Error::new(Kind::Config).with_message(format!(
29 "tls rotate {tls} is not the same family as {}",
30 self.http
31 )));
32 }
33 Ok(Self {
34 tls: tls.hello_rep(),
35 ..self
36 })
37 }
38
39 pub fn rotate_hello(self) -> Result<Self> {
40 let mut hellos: Vec<Browser> = Vec::new();
41 for candidate in Browser::all() {
42 if candidate.family() != self.http.family() {
43 continue;
44 }
45 let rep = candidate.hello_rep();
46 if !hellos.contains(&rep) {
47 hellos.push(rep);
48 }
49 }
50 if hellos.len() < 2 {
51 return Err(Error::new(Kind::Config)
52 .with_message(format!("no other hello in family {}", self.http.family())));
53 }
54 let current = self.tls.hello_rep();
55 let index = hellos.iter().position(|&h| h == current).unwrap_or(0);
56 self.rotate_tls(hellos[(index + 1) % hellos.len()])
57 }
58
59 pub fn switch_family(self, dest: Browser) -> Result<Self> {
60 if dest.family() == self.http.family() {
61 return Err(Error::new(Kind::Config).with_message(format!(
62 "switch_family {dest} is the same family as {} — rotate_tls instead",
63 self.http
64 )));
65 }
66 let id = Self::locked(dest, self.platform);
67 drop(id.user_agent()?);
68 Ok(id)
69 }
70
71 #[must_use]
72 pub fn http(self) -> Browser {
73 self.http
74 }
75
76 #[must_use]
77 pub fn tls(self) -> Browser {
78 self.tls
79 }
80
81 #[must_use]
82 pub fn platform(self) -> Platform {
83 self.platform
84 }
85
86 pub(crate) fn user_agent(self) -> Result<String> {
87 resolve_presented(
88 self.http.platform_profile(self.platform),
89 self.platform,
90 ChromiumBrand::Chrome,
91 )
92 .map(|identity| identity.user_agent)
93 }
94}
95
96#[derive(Debug, Clone, PartialEq, Eq)]
97#[non_exhaustive]
98pub struct SessionIdentity {
99 identity: Option<Identity>,
100 platform: Platform,
101 brand: Option<ChromiumBrand>,
102 user_agent: String,
103}
104
105impl SessionIdentity {
106 #[must_use]
107 pub fn identity(&self) -> Option<Identity> {
108 self.identity
109 }
110
111 #[must_use]
112 pub fn browser(&self) -> Option<Browser> {
113 self.identity.map(Identity::http)
114 }
115
116 #[must_use]
117 pub fn platform(&self) -> Platform {
118 self.platform
119 }
120
121 #[must_use]
122 pub fn brand(&self) -> Option<ChromiumBrand> {
123 self.brand
124 }
125
126 #[must_use]
127 pub fn user_agent(&self) -> &str {
128 &self.user_agent
129 }
130}
131
132impl Session {
133 #[must_use]
134 pub fn identity(&self) -> SessionIdentity {
135 let brand = match self.inner.brand {
136 ChromiumBrand::Chrome => self
137 .inner
138 .browser
139 .filter(|browser| browser.family() == Family::Chrome)
140 .map(|_| ChromiumBrand::Chrome),
141 other => Some(other),
142 };
143 SessionIdentity {
144 identity: self.inner.identity,
145 platform: self.inner.platform,
146 brand,
147 user_agent: self.inner.user_agent.clone(),
148 }
149 }
150
151 pub fn with_identity(&self, identity: Identity) -> Result<Self> {
152 if self.inner.browser.is_none() {
153 return Err(Error::new(Kind::Config)
154 .with_message("with_identity requires an impersonating session"));
155 }
156 let mut session = self.clone();
157 let inner = std::sync::Arc::make_mut(&mut session.inner);
158 let derived = derive_identity(IdentityInputs {
159 source: IdentitySource::Browser {
160 tls: identity.tls(),
161 http: identity.http(),
162 },
163 platform: identity.platform(),
164 brand: inner.brand,
165 compression: inner.compression,
166 #[cfg(feature = "http3")]
167 h3_required: inner.protocol_policy.requires_h3(),
168 tcp: inner.connector.tcp_profile(),
169 audit: inner.audit_tls.is_some(),
170 default_headers: &inner.default_headers,
171 })?;
172 inner.connector = inner
173 .connector
174 .with_profile(&derived.profile)
175 .map_err(Error::from)?;
176 inner.pool = std::sync::Arc::new(inner.pool.fresh());
177 inner.adopt(derived);
178 Ok(session)
179 }
180}
181
182impl SessionInner {
183 fn adopt(&mut self, derived: DerivedIdentity) {
184 let DerivedIdentity {
185 browser,
186 identity,
187 platform,
188 profile,
189 header_style,
190 header_order,
191 user_agent,
192 sec_ch_ua,
193 accept_language,
194 h2_config,
195 #[cfg(feature = "http3")]
196 h3_config,
197 audit_tls,
198 } = derived;
199 self.browser = browser;
200 self.identity = identity;
201 self.platform = platform;
202 self.profile = profile;
203 self.header_style = header_style;
204 self.header_order = header_order;
205 self.user_agent = user_agent;
206 self.sec_ch_ua = sec_ch_ua;
207 self.accept_language = accept_language;
208 self.h2_config = h2_config;
209 #[cfg(feature = "http3")]
210 {
211 self.h3_config = h3_config;
212 }
213 self.audit_tls = audit_tls;
214 }
215}