Skip to main content

leyline/core/session/
builder.rs

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}