1use http::{HeaderMap, HeaderValue};
2
3#[cfg(feature = "http1")]
4use super::core::http1::Http1Options;
5#[cfg(feature = "http2")]
6use super::core::http2::Http2Options;
7use super::layer::config::TransportOptions;
8use crate::{
9 header::{self, OrigHeaderMap},
10 tls::TlsOptions,
11};
12
13pub trait EmulationFactory {
21 fn emulation(self) -> Emulation;
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
31#[non_exhaustive]
32pub enum BrowserProfile {
33 #[default]
35 Chrome,
36 Firefox,
38 Safari,
40 Edge,
42 OkHttp,
44}
45
46impl BrowserProfile {
47 #[inline]
48 const fn user_agent(self) -> &'static str {
49 match self {
50 Self::Chrome => {
51 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36"
52 }
53 Self::Firefox => {
54 "Mozilla/5.0 (Macintosh; Intel Mac OS X 14.0; rv:146.0) Gecko/20100101 Firefox/146.0"
55 }
56 Self::Safari => {
57 "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15"
58 }
59 Self::Edge => {
60 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0"
61 }
62 Self::OkHttp => "okhttp/5.0.0",
63 }
64 }
65
66 #[inline]
67 const fn accept(self) -> &'static str {
68 match self {
69 Self::Firefox => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
70 Self::OkHttp => "*/*",
71 _ => {
72 "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"
73 }
74 }
75 }
76}
77
78#[derive(Debug)]
80#[must_use]
81pub struct EmulationBuilder {
82 emulation: Emulation,
83}
84
85#[derive(Debug, Clone, Default)]
92#[non_exhaustive]
93pub struct Emulation {
94 headers: HeaderMap,
95 orig_headers: OrigHeaderMap,
96 transport: TransportOptions,
97}
98
99impl EmulationBuilder {
102 #[cfg(feature = "http1")]
104 #[inline]
105 pub fn http1_options(mut self, opts: Http1Options) -> Self {
106 *self.emulation.http1_options_mut() = Some(opts);
107 self
108 }
109
110 #[cfg(feature = "http2")]
112 #[inline]
113 pub fn http2_options(mut self, opts: Http2Options) -> Self {
114 *self.emulation.http2_options_mut() = Some(opts);
115 self
116 }
117
118 #[inline]
120 pub fn tls_options(mut self, opts: TlsOptions) -> Self {
121 *self.emulation.tls_options_mut() = Some(opts);
122 self
123 }
124
125 #[inline]
127 pub fn headers(mut self, src: HeaderMap) -> Self {
128 crate::util::replace_headers(&mut self.emulation.headers, src);
129 self
130 }
131
132 #[inline]
134 pub fn orig_headers(mut self, src: OrigHeaderMap) -> Self {
135 self.emulation.orig_headers.extend(src);
136 self
137 }
138
139 #[inline]
141 pub fn build(self) -> Emulation {
142 self.emulation
143 }
144}
145
146impl Emulation {
149 #[inline]
151 pub fn builder() -> EmulationBuilder {
152 EmulationBuilder {
153 emulation: Emulation::default(),
154 }
155 }
156
157 #[inline]
159 pub fn tls_options_mut(&mut self) -> &mut Option<TlsOptions> {
160 self.transport.tls_options_mut()
161 }
162
163 #[cfg(feature = "http1")]
165 #[inline]
166 pub fn http1_options_mut(&mut self) -> &mut Option<Http1Options> {
167 self.transport.http1_options_mut()
168 }
169
170 #[cfg(feature = "http2")]
172 #[inline]
173 pub fn http2_options_mut(&mut self) -> &mut Option<Http2Options> {
174 self.transport.http2_options_mut()
175 }
176
177 #[inline]
179 pub fn headers_mut(&mut self) -> &mut HeaderMap {
180 &mut self.headers
181 }
182
183 #[inline]
185 pub fn orig_headers_mut(&mut self) -> &mut OrigHeaderMap {
186 &mut self.orig_headers
187 }
188
189 #[inline]
191 pub(crate) fn into_parts(self) -> (TransportOptions, HeaderMap, OrigHeaderMap) {
192 (self.transport, self.headers, self.orig_headers)
193 }
194}
195
196impl EmulationFactory for Emulation {
197 #[inline]
198 fn emulation(self) -> Emulation {
199 self
200 }
201}
202
203impl EmulationFactory for BrowserProfile {
204 #[inline]
205 fn emulation(self) -> Emulation {
206 let mut headers = HeaderMap::new();
207 headers.insert(
208 header::USER_AGENT,
209 HeaderValue::from_static(self.user_agent()),
210 );
211 headers.insert(header::ACCEPT, HeaderValue::from_static(self.accept()));
212 headers.insert(
213 header::ACCEPT_LANGUAGE,
214 HeaderValue::from_static("en-US,en;q=0.9"),
215 );
216
217 match self {
218 BrowserProfile::Chrome | BrowserProfile::Edge | BrowserProfile::Safari => {
219 headers.insert("sec-fetch-dest", HeaderValue::from_static("document"));
220 headers.insert("sec-fetch-mode", HeaderValue::from_static("navigate"));
221 headers.insert("sec-fetch-site", HeaderValue::from_static("none"));
222 }
223 BrowserProfile::Firefox | BrowserProfile::OkHttp => {}
224 }
225
226 Emulation::builder().headers(headers).build()
227 }
228}
229
230#[cfg(feature = "http1")]
231impl EmulationFactory for Http1Options {
232 #[inline]
233 fn emulation(self) -> Emulation {
234 Emulation::builder().http1_options(self).build()
235 }
236}
237
238#[cfg(feature = "http2")]
239impl EmulationFactory for Http2Options {
240 #[inline]
241 fn emulation(self) -> Emulation {
242 Emulation::builder().http2_options(self).build()
243 }
244}
245
246impl EmulationFactory for TlsOptions {
247 #[inline]
248 fn emulation(self) -> Emulation {
249 Emulation::builder().tls_options(self).build()
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 #[test]
258 fn browser_profile_sets_user_agent_header() {
259 let emulation = BrowserProfile::Firefox.emulation();
260 let headers = emulation.headers;
261 assert!(headers.contains_key(header::USER_AGENT));
262 }
263}