1use std::sync::Arc;
2
3use crate::cookie::Jar;
4use crate::core::config::HostLimits;
5use crate::core::proxy_pool::ProxyPool;
6use crate::core::{
7 CompressionConfig, DnsConfig, IntoParamPair, IntoUrl, PoolConfig, ProxyConfig, RedirectPolicy,
8 SocketConfig, TimeoutConfig, WebSocketConfig,
9};
10use crate::profile::{Browser, BrowserProfile, ChromiumBrand, Platform};
11use crate::tcp::TcpProfile;
12use crate::tls::TlsTrustConfig;
13use crate::trace::Trace;
14
15pub(crate) use self::bearer::BearerToken;
16use self::derive::{IdentityInputs, IdentitySource, derive_identity};
17use super::proxy::InvalidEnvProxy;
18use super::{Identity, ProtocolPolicy, Session};
19use crate::core::error::Result;
20
21const AUTHORIZATION: &str = "authorization";
22const USER_AGENT: &str = "user-agent";
23
24mod assemble;
25mod bearer;
26pub(super) mod connect;
27mod debug;
28pub(super) mod derive;
29mod validate;
30
31#[must_use = "builders are lazy: nothing happens until `.send()` / `.build()`"]
32pub struct SessionBuilder {
33 browser: Option<Browser>,
34 profile: Option<Arc<BrowserProfile>>,
35 platform: Platform,
36 platform_explicit: bool,
37 brand: ChromiumBrand,
38 proxy_config: ProxyConfig,
39 dns_config: DnsConfig,
40 timeouts: TimeoutConfig,
41 pool_config: PoolConfig,
42 socket_config: SocketConfig,
43 redirect_policy: RedirectPolicy,
44 compression: CompressionConfig,
45 expected_profile_id: Option<String>,
46 websocket_config: WebSocketConfig,
47 https_only: bool,
48 audit: bool,
49 cookie_jar: Option<Jar>,
50 tcp_profile: Option<TcpProfile>,
51 protocol_policy: ProtocolPolicy,
52 #[cfg(feature = "http3")]
53 protocol_explicit: bool,
54 config_error: Option<String>,
55 default_headers: Vec<(String, String)>,
56 bearer: Option<BearerToken>,
57 http_identity: Option<Browser>,
58 tls_trust: TlsTrustConfig,
59 default_retry: crate::core::retry::RetryPolicy,
60 trace: Option<Arc<dyn Trace>>,
61 base_url: Option<url::Url>,
62 languages: Option<Vec<String>>,
63 host_limits: HostLimits,
64 proxy_pool: Option<ProxyPool>,
65}
66
67impl SessionBuilder {
68 pub(super) fn new() -> Self {
69 Self {
70 browser: None,
71 profile: None,
72 platform: Platform::default(),
73 platform_explicit: false,
74 brand: ChromiumBrand::default(),
75 proxy_config: ProxyConfig::default(),
76 dns_config: DnsConfig::default(),
77 timeouts: TimeoutConfig::default(),
78 pool_config: PoolConfig::default(),
79 socket_config: SocketConfig::default(),
80 redirect_policy: RedirectPolicy::default(),
81 compression: CompressionConfig::default(),
82 expected_profile_id: None,
83 websocket_config: WebSocketConfig::default(),
84 https_only: false,
85 audit: false,
86 cookie_jar: None,
87 tcp_profile: None,
88 protocol_policy: ProtocolPolicy::Auto,
89 #[cfg(feature = "http3")]
90 protocol_explicit: false,
91 config_error: None,
92 default_headers: Vec::new(),
93 bearer: None,
94 http_identity: None,
95 tls_trust: TlsTrustConfig::default(),
96 default_retry: crate::core::retry::RetryPolicy::none(),
97 trace: None,
98 base_url: None,
99 languages: None,
100 host_limits: HostLimits::default(),
101 proxy_pool: None,
102 }
103 }
104
105 pub fn proxy(mut self, config: impl Into<ProxyConfig>) -> Self {
106 self.proxy_config = config.into();
107 self
108 }
109
110 pub fn timeout(mut self, config: impl Into<TimeoutConfig>) -> Self {
111 self.timeouts = config.into();
112 self
113 }
114
115 pub fn retry(mut self, policy: crate::core::retry::RetryPolicy) -> Self {
116 self.default_retry = policy;
117 self
118 }
119
120 pub fn pool(mut self, config: PoolConfig) -> Self {
121 self.pool_config = config;
122 self
123 }
124
125 pub fn browser(mut self, browser: Browser) -> Self {
126 self.profile = None;
127 self.http_identity = None;
128 self.browser = Some(browser);
129 self
130 }
131
132 pub fn profile(mut self, profile: BrowserProfile) -> Self {
133 self.browser = None;
134 self.http_identity = None;
135 self.profile = Some(Arc::new(profile));
136 self
137 }
138
139 fn impersonates(&self) -> bool {
140 self.browser.is_some() || self.profile.is_some()
141 }
142
143 fn identity_source(&self) -> IdentitySource {
144 match (&self.profile, self.browser) {
145 (Some(profile), _) => IdentitySource::Profile(Arc::clone(profile)),
146 (None, Some(tls)) => IdentitySource::Browser {
147 tls,
148 http: self.http_identity.unwrap_or(tls),
149 },
150 (None, None) => IdentitySource::Bare,
151 }
152 }
153
154 fn identity_inputs<'a>(&'a self, tcp: &'a TcpProfile) -> IdentityInputs<'a> {
155 IdentityInputs {
156 source: self.identity_source(),
157 platform: self.platform,
158 brand: self.brand,
159 compression: self.compression,
160 #[cfg(feature = "http3")]
161 h3_required: self.protocol_policy.requires_h3(),
162 tcp,
163 audit: self.audit,
164 default_headers: &self.default_headers,
165 languages: self.languages.as_deref(),
166 }
167 }
168
169 pub fn brand(mut self, brand: ChromiumBrand) -> Self {
170 self.brand = brand;
171 self
172 }
173
174 pub fn platform(mut self, platform: Platform) -> Self {
175 self.platform = platform;
176 self.platform_explicit = true;
177 self
178 }
179
180 pub fn redirect(mut self, policy: RedirectPolicy) -> Self {
181 self.redirect_policy = policy;
182 self
183 }
184
185 pub fn cookie_jar(mut self, jar: Jar) -> Self {
186 self.cookie_jar = Some(jar);
187 self
188 }
189
190 pub fn tcp_profile(mut self, profile: TcpProfile) -> Self {
191 self.tcp_profile = Some(profile);
192 self
193 }
194
195 pub fn dns(mut self, config: impl Into<DnsConfig>) -> Self {
196 self.dns_config = config.into();
197 self
198 }
199
200 pub fn tls_trust(mut self, trust: TlsTrustConfig) -> Self {
201 self.tls_trust = trust;
202 self
203 }
204
205 pub fn socket(mut self, config: SocketConfig) -> Self {
206 self.socket_config = config;
207 self
208 }
209
210 pub fn compression(mut self, config: CompressionConfig) -> Self {
211 self.compression = config;
212 self
213 }
214
215 pub fn expect_profile_id(mut self, id: &str) -> Self {
216 self.expected_profile_id = Some(id.to_owned());
217 self
218 }
219
220 pub fn websocket_config(mut self, config: WebSocketConfig) -> Self {
221 self.websocket_config = config;
222 self
223 }
224
225 pub fn https_only(mut self, enabled: bool) -> Self {
226 self.https_only = enabled;
227 self
228 }
229
230 pub fn trace(mut self, hook: impl Trace) -> Self {
231 self.trace = Some(Arc::new(hook));
232 self
233 }
234
235 pub fn audit(mut self, enabled: bool) -> Self {
236 self.audit = enabled;
237 self
238 }
239
240 pub fn protocol(mut self, policy: ProtocolPolicy) -> Self {
241 self.protocol_policy = policy;
242 #[cfg(feature = "http3")]
243 {
244 self.protocol_explicit = true;
245 }
246 self
247 }
248
249 pub fn identity(mut self, id: Identity) -> Self {
250 self = self.browser(id.tls()).platform(id.platform());
251 self.http_identity = (id.http() != id.tls()).then(|| id.http());
252 if let Some(brand) = id.brand() {
253 self.brand = brand;
254 }
255 self
256 }
257
258 pub fn user_agent(mut self, value: &str) -> Self {
259 self.check_header(USER_AGENT, value);
260 self.remove_default(USER_AGENT);
261 self.default_headers
262 .push((USER_AGENT.to_owned(), value.to_owned()));
263 self
264 }
265
266 pub fn languages<I, S>(mut self, langs: I) -> Self
267 where
268 I: IntoIterator<Item = S>,
269 S: AsRef<str>,
270 {
271 let tags: Vec<String> = langs
272 .into_iter()
273 .map(|tag| tag.as_ref().trim().to_owned())
274 .collect();
275 let checked = if tags.is_empty() {
276 Err("languages needs at least one language tag".to_owned())
277 } else {
278 tags.iter()
279 .try_for_each(|tag| crate::profile::languages::validate_language(tag))
280 };
281 if let Err(error) = checked {
282 self.config_error.get_or_insert(error);
283 }
284 self.languages = Some(tags);
285 self
286 }
287
288 pub fn host_limits(mut self, limits: HostLimits) -> Self {
289 if let Some(error) = limits.config_error() {
290 self.config_error.get_or_insert(error);
291 }
292 self.host_limits = limits;
293 self
294 }
295
296 pub fn proxy_pool(mut self, pool: ProxyPool) -> Self {
297 self.proxy_pool = Some(pool);
298 self
299 }
300
301 pub fn base_url(mut self, url: impl IntoUrl) -> Self {
302 match super::helpers::base_url(url) {
303 Ok(url) => self.base_url = Some(url),
304 Err(error) => {
305 self.base_url = None;
306 self.config_error.get_or_insert(error.to_string());
307 }
308 }
309 self
310 }
311
312 pub fn headers<I, P>(mut self, headers: I) -> Self
313 where
314 I: IntoIterator<Item = P>,
315 P: IntoParamPair,
316 {
317 for pair in headers {
318 let (name, value) = pair.into_param_pair();
319 self.check_header(&name, &value);
320 self.push_default(name, value);
321 }
322 self
323 }
324
325 fn check_header(&mut self, name: &str, value: &str) {
326 if self.config_error.is_none()
327 && (crate::core::headers::name(name).is_err()
328 || crate::core::headers::value(value).is_err())
329 {
330 self.config_error = Some(format!("invalid header `{}`", name.escape_debug()));
331 }
332 }
333
334 pub(super) fn into_builtin(self) -> Session {
335 self.build_with(InvalidEnvProxy::FailRequests)
336 .expect("bundled profile data is statically valid; default trust loading only warns")
337 }
338
339 pub fn build(self) -> Result<Session> {
340 self.build_with(InvalidEnvProxy::FailBuild)
341 }
342
343 fn build_with(mut self, on_invalid: InvalidEnvProxy) -> Result<Session> {
344 self.validate(on_invalid)?;
345 let tcp_profile = self.resolve_tcp_profile();
346 let derived = derive_identity(self.identity_inputs(&tcp_profile))?;
347 let connector = self.build_connector(&derived.profile, &tcp_profile)?;
348 let expected = self.expected_profile_id.take();
349 let session = self.into_session(derived, connector);
350 Self::check_profile_id(expected.as_deref(), session.identity().profile_id())?;
351 Ok(session)
352 }
353}