use http::{HeaderMap, HeaderValue};
#[cfg(feature = "http1")]
use super::core::http1::Http1Options;
#[cfg(feature = "http2")]
use super::core::http2::Http2Options;
use super::layer::config::TransportOptions;
use crate::{
header::{self, OrigHeaderMap},
tls::TlsOptions,
};
pub trait EmulationFactory {
fn emulation(self) -> Emulation;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum BrowserProfile {
#[default]
Chrome,
Firefox,
Safari,
Edge,
OkHttp,
}
impl BrowserProfile {
#[inline]
const fn user_agent(self) -> &'static str {
match self {
Self::Chrome => {
"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"
}
Self::Firefox => {
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14.0; rv:146.0) Gecko/20100101 Firefox/146.0"
}
Self::Safari => {
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15"
}
Self::Edge => {
"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"
}
Self::OkHttp => "okhttp/5.0.0",
}
}
#[inline]
const fn accept(self) -> &'static str {
match self {
Self::Firefox => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
Self::OkHttp => "*/*",
_ => {
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"
}
}
}
}
#[derive(Debug)]
#[must_use]
pub struct EmulationBuilder {
emulation: Emulation,
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct Emulation {
headers: HeaderMap,
orig_headers: OrigHeaderMap,
transport: TransportOptions,
}
impl EmulationBuilder {
#[cfg(feature = "http1")]
#[inline]
pub fn http1_options(mut self, opts: Http1Options) -> Self {
*self.emulation.http1_options_mut() = Some(opts);
self
}
#[cfg(feature = "http2")]
#[inline]
pub fn http2_options(mut self, opts: Http2Options) -> Self {
*self.emulation.http2_options_mut() = Some(opts);
self
}
#[inline]
pub fn tls_options(mut self, opts: TlsOptions) -> Self {
*self.emulation.tls_options_mut() = Some(opts);
self
}
#[inline]
pub fn headers(mut self, src: HeaderMap) -> Self {
crate::util::replace_headers(&mut self.emulation.headers, src);
self
}
#[inline]
pub fn orig_headers(mut self, src: OrigHeaderMap) -> Self {
self.emulation.orig_headers.extend(src);
self
}
#[inline]
pub fn build(self) -> Emulation {
self.emulation
}
}
impl Emulation {
#[inline]
pub fn builder() -> EmulationBuilder {
EmulationBuilder {
emulation: Emulation::default(),
}
}
#[inline]
pub fn tls_options_mut(&mut self) -> &mut Option<TlsOptions> {
self.transport.tls_options_mut()
}
#[cfg(feature = "http1")]
#[inline]
pub fn http1_options_mut(&mut self) -> &mut Option<Http1Options> {
self.transport.http1_options_mut()
}
#[cfg(feature = "http2")]
#[inline]
pub fn http2_options_mut(&mut self) -> &mut Option<Http2Options> {
self.transport.http2_options_mut()
}
#[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.headers
}
#[inline]
pub fn orig_headers_mut(&mut self) -> &mut OrigHeaderMap {
&mut self.orig_headers
}
#[inline]
pub(crate) fn into_parts(self) -> (TransportOptions, HeaderMap, OrigHeaderMap) {
(self.transport, self.headers, self.orig_headers)
}
}
impl EmulationFactory for Emulation {
#[inline]
fn emulation(self) -> Emulation {
self
}
}
impl EmulationFactory for BrowserProfile {
#[inline]
fn emulation(self) -> Emulation {
let mut headers = HeaderMap::new();
headers.insert(
header::USER_AGENT,
HeaderValue::from_static(self.user_agent()),
);
headers.insert(header::ACCEPT, HeaderValue::from_static(self.accept()));
headers.insert(
header::ACCEPT_LANGUAGE,
HeaderValue::from_static("en-US,en;q=0.9"),
);
match self {
BrowserProfile::Chrome | BrowserProfile::Edge | BrowserProfile::Safari => {
headers.insert("sec-fetch-dest", HeaderValue::from_static("document"));
headers.insert("sec-fetch-mode", HeaderValue::from_static("navigate"));
headers.insert("sec-fetch-site", HeaderValue::from_static("none"));
}
BrowserProfile::Firefox | BrowserProfile::OkHttp => {}
}
Emulation::builder().headers(headers).build()
}
}
#[cfg(feature = "http1")]
impl EmulationFactory for Http1Options {
#[inline]
fn emulation(self) -> Emulation {
Emulation::builder().http1_options(self).build()
}
}
#[cfg(feature = "http2")]
impl EmulationFactory for Http2Options {
#[inline]
fn emulation(self) -> Emulation {
Emulation::builder().http2_options(self).build()
}
}
impl EmulationFactory for TlsOptions {
#[inline]
fn emulation(self) -> Emulation {
Emulation::builder().tls_options(self).build()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn browser_profile_sets_user_agent_header() {
let emulation = BrowserProfile::Firefox.emulation();
let headers = emulation.headers;
assert!(headers.contains_key(header::USER_AGENT));
}
}