1use std::sync::Arc;
2
3use crate::cookie::Jar;
4use crate::core::{
5 CompressionConfig, DnsConfig, IntoParamPair, PoolConfig, ProxyConfig, RedirectPolicy,
6 SocketConfig, TimeoutConfig, WebSocketConfig,
7};
8use crate::profile::{Browser, BrowserProfile, ChromiumBrand, Platform};
9use crate::tcp::TcpProfile;
10use crate::tls::TlsTrustConfig;
11use crate::trace::Trace;
12
13use self::derive::{IdentityInputs, IdentitySource, derive_identity};
14use super::proxy::InvalidEnvProxy;
15use super::{Identity, ProtocolPolicy, Session};
16use crate::core::error::Result;
17
18mod assemble;
19pub(super) mod connect;
20mod debug;
21pub(super) mod derive;
22mod validate;
23
24#[must_use = "builders are lazy: nothing happens until `.send()` / `.build()`"]
25pub struct SessionBuilder {
26 browser: Option<Browser>,
27 profile: Option<Arc<BrowserProfile>>,
28 platform: Platform,
29 platform_explicit: bool,
30 brand: ChromiumBrand,
31 proxy_config: ProxyConfig,
32 dns_config: DnsConfig,
33 timeouts: TimeoutConfig,
34 pool_config: PoolConfig,
35 socket_config: SocketConfig,
36 redirect_policy: RedirectPolicy,
37 compression: CompressionConfig,
38 websocket_config: WebSocketConfig,
39 https_only: bool,
40 audit: bool,
41 cookie_jar: Option<Jar>,
42 tcp_profile: Option<TcpProfile>,
43 protocol_policy: ProtocolPolicy,
44 config_error: Option<String>,
45 default_headers: Vec<(String, String)>,
46 http_identity: Option<Browser>,
47 tls_trust: TlsTrustConfig,
48 default_retry: crate::core::retry::RetryPolicy,
49 trace: Option<Arc<dyn Trace>>,
50}
51
52impl SessionBuilder {
53 pub(super) fn new() -> Self {
54 Self {
55 browser: None,
56 profile: None,
57 platform: Platform::default(),
58 platform_explicit: false,
59 brand: ChromiumBrand::default(),
60 proxy_config: ProxyConfig::default(),
61 dns_config: DnsConfig::default(),
62 timeouts: TimeoutConfig::default(),
63 pool_config: PoolConfig::default(),
64 socket_config: SocketConfig::default(),
65 redirect_policy: RedirectPolicy::default(),
66 compression: CompressionConfig::default(),
67 websocket_config: WebSocketConfig::default(),
68 https_only: false,
69 audit: false,
70 cookie_jar: None,
71 tcp_profile: None,
72 protocol_policy: ProtocolPolicy::Auto,
73 config_error: None,
74 default_headers: Vec::new(),
75 http_identity: None,
76 tls_trust: TlsTrustConfig::default(),
77 default_retry: crate::core::retry::RetryPolicy::none(),
78 trace: None,
79 }
80 }
81
82 pub fn proxy(mut self, config: impl Into<ProxyConfig>) -> Self {
83 self.proxy_config = config.into();
84 self
85 }
86
87 pub fn timeout(mut self, config: impl Into<TimeoutConfig>) -> Self {
88 self.timeouts = config.into();
89 self
90 }
91
92 pub fn retry(mut self, policy: crate::core::retry::RetryPolicy) -> Self {
93 self.default_retry = policy;
94 self
95 }
96
97 pub fn pool(mut self, config: PoolConfig) -> Self {
98 self.pool_config = config;
99 self
100 }
101
102 pub fn browser(mut self, browser: Browser) -> Self {
103 self.profile = None;
104 self.http_identity = None;
105 self.browser = Some(browser);
106 self
107 }
108
109 pub fn profile(mut self, profile: BrowserProfile) -> Self {
110 self.browser = None;
111 self.http_identity = None;
112 self.profile = Some(Arc::new(profile));
113 self
114 }
115
116 fn impersonates(&self) -> bool {
117 self.browser.is_some() || self.profile.is_some()
118 }
119
120 fn identity_source(&self) -> IdentitySource {
121 match (&self.profile, self.browser) {
122 (Some(profile), _) => IdentitySource::Profile(Arc::clone(profile)),
123 (None, Some(tls)) => IdentitySource::Browser {
124 tls,
125 http: self.http_identity.unwrap_or(tls),
126 },
127 (None, None) => IdentitySource::Bare,
128 }
129 }
130
131 fn identity_inputs<'a>(&'a self, tcp: &'a TcpProfile) -> IdentityInputs<'a> {
132 IdentityInputs {
133 source: self.identity_source(),
134 platform: self.platform,
135 brand: self.brand,
136 compression: self.compression,
137 #[cfg(feature = "http3")]
138 h3_required: self.protocol_policy.requires_h3(),
139 tcp,
140 audit: self.audit,
141 default_headers: &self.default_headers,
142 }
143 }
144
145 pub fn brand(mut self, brand: ChromiumBrand) -> Self {
146 self.brand = brand;
147 self
148 }
149
150 pub fn platform(mut self, platform: Platform) -> Self {
151 self.platform = platform;
152 self.platform_explicit = true;
153 self
154 }
155
156 pub fn redirect(mut self, policy: RedirectPolicy) -> Self {
157 self.redirect_policy = policy;
158 self
159 }
160
161 pub fn cookie_jar(mut self, jar: Jar) -> Self {
162 self.cookie_jar = Some(jar);
163 self
164 }
165
166 pub fn tcp_profile(mut self, profile: TcpProfile) -> Self {
167 self.tcp_profile = Some(profile);
168 self
169 }
170
171 pub fn dns(mut self, config: impl Into<DnsConfig>) -> Self {
172 self.dns_config = config.into();
173 self
174 }
175
176 pub fn tls_trust(mut self, trust: TlsTrustConfig) -> Self {
177 self.tls_trust = trust;
178 self
179 }
180
181 pub fn socket(mut self, config: SocketConfig) -> Self {
182 self.socket_config = config;
183 self
184 }
185
186 pub fn compression(mut self, config: CompressionConfig) -> Self {
187 self.compression = config;
188 self
189 }
190
191 pub fn websocket_config(mut self, config: WebSocketConfig) -> Self {
192 self.websocket_config = config;
193 self
194 }
195
196 pub fn https_only(mut self, enabled: bool) -> Self {
197 self.https_only = enabled;
198 self
199 }
200
201 pub fn trace(mut self, hook: impl Trace) -> Self {
202 self.trace = Some(Arc::new(hook));
203 self
204 }
205
206 pub fn audit(mut self, enabled: bool) -> Self {
207 self.audit = enabled;
208 self
209 }
210
211 pub fn protocol(mut self, policy: ProtocolPolicy) -> Self {
212 self.protocol_policy = policy;
213 self
214 }
215
216 pub fn identity(mut self, id: Identity) -> Self {
217 self = self.browser(id.tls()).platform(id.platform());
218 self.http_identity = (id.http() != id.tls()).then(|| id.http());
219 self
220 }
221
222 pub fn headers<I, P>(mut self, headers: I) -> Self
223 where
224 I: IntoIterator<Item = P>,
225 P: IntoParamPair,
226 {
227 for pair in headers {
228 let (name, value) = pair.into_param_pair();
229 self.check_header(&name, &value);
230 self.default_headers.push((name, value));
231 }
232 self
233 }
234
235 fn check_header(&mut self, name: &str, value: &str) {
236 if self.config_error.is_none()
237 && (crate::core::headers::name(name).is_err()
238 || crate::core::headers::value(value).is_err())
239 {
240 self.config_error = Some(format!("invalid header `{}`", name.escape_debug()));
241 }
242 }
243
244 pub(super) fn into_builtin(self) -> Session {
245 self.build_with(InvalidEnvProxy::FailRequests)
246 .expect("bundled profile data is statically valid; default trust loading only warns")
247 }
248
249 pub fn build(self) -> Result<Session> {
250 self.build_with(InvalidEnvProxy::FailBuild)
251 }
252
253 fn build_with(mut self, on_invalid: InvalidEnvProxy) -> Result<Session> {
254 self.validate(on_invalid)?;
255 let tcp_profile = self.resolve_tcp_profile();
256 let derived = derive_identity(self.identity_inputs(&tcp_profile))?;
257 let connector = self.build_connector(&derived.profile, &tcp_profile)?;
258 Ok(self.into_session(derived, connector))
259 }
260}