From 3c7dbded2c248b3aa74ad5dbea976eb35ea428a7 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Mon, 4 Aug 2025 09:14:59 +0800 Subject: [PATCH 1/3] build(deps): update wreq requirement from 5 to 6.0.0-rc.1 --- Cargo.toml | 34 ++- src/emulation/device/chrome.rs | 337 +++++++++++----------- src/emulation/device/firefox.rs | 480 +++++++++++++++++--------------- src/emulation/device/macros.rs | 6 - src/emulation/device/mod.rs | 22 +- src/emulation/device/okhttp.rs | 225 ++++++++------- src/emulation/device/opera.rs | 213 +++++++------- src/emulation/device/safari.rs | 379 ++++++++++++++----------- src/emulation/mod.rs | 19 +- src/emulation/rand.rs | 21 +- tests/emulation_chrome.rs | 5 +- tests/emulation_firefox.rs | 1 - tests/emulation_okhttp.rs | 1 - tests/emulation_opera.rs | 5 +- tests/emulation_safari.rs | 1 - tests/support/delay_server.rs | 9 +- tests/support/mod.rs | 14 +- tests/support/server.rs | 16 +- 18 files changed, 947 insertions(+), 841 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a0cf90e..a5501c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,11 +37,11 @@ deflate = ["wreq/deflate"] zstd = ["wreq/zstd"] [dependencies] -wreq = { version = ">=3.0.5,<6", default-features = false } +wreq = { version = "6.0.0-rc.1", default-features = false } serde = { version = "1.0", features = ["derive"], optional = true } typed-builder = { version = "0.21.0", optional = true } -strum = { version = "0.27.1", optional = true } -strum_macros = { version = "0.27.1", optional = true } +strum = { version = "0.27.2", optional = true } +strum_macros = { version = "0.27.2", optional = true } [dev-dependencies] tokio = { version = "1", features = ["full"] } @@ -93,14 +93,36 @@ path = "tests/emulation_safari.rs" [[example]] name = "emulation" path = "examples/emulation.rs" -required-features = ["emulation", "gzip", "brotli", "deflate", "zstd", "wreq/full"] +required-features = [ + "emulation", + "gzip", + "brotli", + "deflate", + "zstd", + "wreq/full", +] [[example]] name = "emulation_rand" path = "examples/emulation_rand.rs" -required-features = ["emulation", "gzip", "brotli", "deflate", "zstd", "wreq/full", "emulation-rand"] +required-features = [ + "emulation", + "gzip", + "brotli", + "deflate", + "zstd", + "wreq/full", + "emulation-rand", +] [[example]] name = "emulation_option" path = "examples/emulation_option.rs" -required-features = ["emulation", "gzip", "brotli", "deflate", "zstd", "wreq/full"] +required-features = [ + "emulation", + "gzip", + "brotli", + "deflate", + "zstd", + "wreq/full", +] diff --git a/src/emulation/device/chrome.rs b/src/emulation/device/chrome.rs index 78609db..219fbb6 100644 --- a/src/emulation/device/chrome.rs +++ b/src/emulation/device/chrome.rs @@ -1,23 +1,66 @@ -use super::emulation_imports::*; -use super::*; -use http2::*; +use header::*; use tls::*; -macro_rules! tls_config { +use super::{emulation_imports::*, http2_imports::*, *}; + +macro_rules! headers_stream_dependency { + () => { + StreamDependency::new(StreamId::zero(), 255, true) + }; +} + +macro_rules! pseudo_order { + () => { + PseudoOrder::builder() + .extend([ + PseudoId::Method, + PseudoId::Authority, + PseudoId::Scheme, + PseudoId::Path, + ]) + .build() + }; +} + +macro_rules! settings_order { + () => { + SettingsOrder::builder() + .extend([ + SettingId::HeaderTableSize, + SettingId::EnablePush, + SettingId::MaxConcurrentStreams, + SettingId::InitialWindowSize, + SettingId::MaxFrameSize, + SettingId::MaxHeaderListSize, + SettingId::EnableConnectProtocol, + SettingId::NoRfc7540Priorities, + ]) + .build() + }; +} + +macro_rules! tls_options { (1) => { - ChromeTlsConfig::builder().build() + ChromeTlsConfig::builder().build().into() }; (2) => { - ChromeTlsConfig::builder().enable_ech_grease(true).build() + ChromeTlsConfig::builder() + .enable_ech_grease(true) + .build() + .into() }; (3) => { - ChromeTlsConfig::builder().permute_extensions(true).build() + ChromeTlsConfig::builder() + .permute_extensions(true) + .build() + .into() }; (4) => { ChromeTlsConfig::builder() .permute_extensions(true) .enable_ech_grease(true) .build() + .into() }; (5) => { ChromeTlsConfig::builder() @@ -25,6 +68,7 @@ macro_rules! tls_config { .enable_ech_grease(true) .pre_shared_key(true) .build() + .into() }; (6, $curves:expr) => { ChromeTlsConfig::builder() @@ -33,6 +77,7 @@ macro_rules! tls_config { .pre_shared_key(true) .enable_ech_grease(true) .build() + .into() }; (7, $curves:expr) => { ChromeTlsConfig::builder() @@ -42,128 +87,122 @@ macro_rules! tls_config { .enable_ech_grease(true) .alps_use_new_codepoint(true) .build() + .into() }; } -macro_rules! http2_config { +macro_rules! http2_options { (1) => { - Http2Config::builder() - .initial_stream_window_size(6291456) + Http2Options::builder() + .initial_window_size(6291456) .initial_connection_window_size(15728640) .max_concurrent_streams(1000) .max_header_list_size(262144) .header_table_size(65536) - .headers_priority(HEADER_PRIORITY) - .headers_pseudo_order(HEADERS_PSEUDO_ORDER) - .settings_order(SETTINGS_ORDER) + .headers_stream_dependency(headers_stream_dependency!()) + .headers_pseudo_order(pseudo_order!()) + .settings_order(settings_order!()) .build() }; (2) => { - Http2Config::builder() - .initial_stream_window_size(6291456) + Http2Options::builder() + .initial_window_size(6291456) .initial_connection_window_size(15728640) .max_concurrent_streams(1000) .max_header_list_size(262144) .header_table_size(65536) .enable_push(false) - .headers_priority(HEADER_PRIORITY) - .headers_pseudo_order(HEADERS_PSEUDO_ORDER) - .settings_order(SETTINGS_ORDER) + .headers_stream_dependency(headers_stream_dependency!()) + .headers_pseudo_order(pseudo_order!()) + .settings_order(settings_order!()) .build() }; (3) => { - Http2Config::builder() - .initial_stream_window_size(6291456) + Http2Options::builder() + .initial_window_size(6291456) .initial_connection_window_size(15728640) .max_header_list_size(262144) .header_table_size(65536) .enable_push(false) - .headers_priority(HEADER_PRIORITY) - .headers_pseudo_order(HEADERS_PSEUDO_ORDER) - .settings_order(SETTINGS_ORDER) + .headers_stream_dependency(headers_stream_dependency!()) + .headers_pseudo_order(pseudo_order!()) + .settings_order(settings_order!()) .build() }; } -#[inline] -fn header_initializer( - sec_ch_ua: &'static str, - ua: &'static str, - emulation_os: EmulationOS, -) -> HeaderMap { - let mut headers = HeaderMap::new(); - header_chrome_sec_ch_ua!( - headers, - sec_ch_ua, - emulation_os.platform(), - emulation_os.is_mobile() - ); - header_chrome_ua!(headers, ua); - header_chrome_sec_fetch!(headers); - header_chrome_accpet!(headers); - headers -} +mod header { + + use super::*; + + #[inline] + pub fn header_initializer( + sec_ch_ua: &'static str, + ua: &'static str, + emulation_os: EmulationOS, + ) -> HeaderMap { + let mut headers = HeaderMap::new(); + header_chrome_sec_ch_ua!( + headers, + sec_ch_ua, + emulation_os.platform(), + emulation_os.is_mobile() + ); + header_chrome_ua!(headers, ua); + header_chrome_sec_fetch!(headers); + header_chrome_accpet!(headers); + headers + } -#[inline] -fn header_initializer_with_zstd( - sec_ch_ua: &'static str, - ua: &'static str, - emulation_os: EmulationOS, -) -> HeaderMap { - let mut headers = HeaderMap::new(); - header_chrome_sec_ch_ua!( - headers, - sec_ch_ua, - emulation_os.platform(), - emulation_os.is_mobile() - ); - header_chrome_ua!(headers, ua); - header_chrome_sec_fetch!(headers); - header_chrome_accpet!(zstd, headers); - headers -} + #[inline] + pub fn header_initializer_with_zstd( + sec_ch_ua: &'static str, + ua: &'static str, + emulation_os: EmulationOS, + ) -> HeaderMap { + let mut headers = HeaderMap::new(); + header_chrome_sec_ch_ua!( + headers, + sec_ch_ua, + emulation_os.platform(), + emulation_os.is_mobile() + ); + header_chrome_ua!(headers, ua); + header_chrome_sec_fetch!(headers); + header_chrome_accpet!(zstd, headers); + headers + } -#[inline] -fn header_initializer_with_zstd_priority( - sec_ch_ua: &'static str, - ua: &'static str, - emulation_os: EmulationOS, -) -> HeaderMap { - let mut headers = HeaderMap::new(); - header_chrome_sec_ch_ua!( - headers, - sec_ch_ua, - emulation_os.platform(), - emulation_os.is_mobile() - ); - header_chrome_ua!(headers, ua); - header_chrome_sec_fetch!(headers); - header_chrome_accpet!(zstd, headers); - headers.insert( - HeaderName::from_static("priority"), - HeaderValue::from_static("u=0, i"), - ); - headers + #[inline] + pub fn header_initializer_with_zstd_priority( + sec_ch_ua: &'static str, + ua: &'static str, + emulation_os: EmulationOS, + ) -> HeaderMap { + let mut headers = HeaderMap::new(); + header_chrome_sec_ch_ua!( + headers, + sec_ch_ua, + emulation_os.platform(), + emulation_os.is_mobile() + ); + header_chrome_ua!(headers, ua); + header_chrome_sec_fetch!(headers); + header_chrome_accpet!(zstd, headers); + headers.insert( + HeaderName::from_static("priority"), + HeaderValue::from_static("u=0, i"), + ); + headers + } } mod tls { use super::tls_imports::*; - pub const CURVES_1: &[SslCurve] = &[SslCurve::X25519, SslCurve::SECP256R1, SslCurve::SECP384R1]; - - pub const CURVES_2: &[SslCurve] = &[ - SslCurve::X25519_KYBER768_DRAFT00, - SslCurve::X25519, - SslCurve::SECP256R1, - SslCurve::SECP384R1, - ]; - - pub const CURVES_3: &[SslCurve] = &[ - SslCurve::X25519_MLKEM768, - SslCurve::X25519, - SslCurve::SECP256R1, - SslCurve::SECP384R1, - ]; + pub const CURVES_1: &str = join!(":", "X25519", "P-256", "P-384"); + pub const CURVES_2: &str = join!(":", "X25519Kyber768Draft00", "X25519", "P-256", "P-384"); + pub const CURVES_3: &str = join!(":", "X25519MLKEM768", "X25519", "P-256", "P-384"); pub const CIPHER_LIST: &str = join!( ":", @@ -196,13 +235,13 @@ mod tls { "rsa_pkcs1_sha512" ); - pub const CERT_COMPRESSION_ALGORITHM: &[CertCompressionAlgorithm] = - &[CertCompressionAlgorithm::Brotli]; + pub const CERT_COMPRESSION_ALGORITHM: &[CertificateCompressionAlgorithm] = + &[CertificateCompressionAlgorithm::BROTLI]; #[derive(TypedBuilder)] pub struct ChromeTlsConfig { #[builder(default = CURVES_1)] - curves: &'static [SslCurve], + curves: &'static str, #[builder(default = SIGALGS_LIST)] sigalgs_list: &'static str, @@ -210,8 +249,8 @@ mod tls { #[builder(default = CIPHER_LIST)] cipher_list: &'static str, - #[builder(default = AlpsProtos::HTTP2, setter(into))] - alps_protos: AlpsProtos, + #[builder(default = AlpsProtocol::HTTP2, setter(into))] + alps_protos: AlpsProtocol, #[builder(default = false)] alps_use_new_codepoint: bool, @@ -226,13 +265,13 @@ mod tls { pre_shared_key: bool, } - impl From for TlsConfig { + impl From for TlsOptions { fn from(val: ChromeTlsConfig) -> Self { - TlsConfig::builder() + TlsOptions::builder() .grease_enabled(true) .enable_ocsp_stapling(true) .enable_signed_cert_timestamps(true) - .curves(val.curves) + .curves_list(val.curves) .sigalgs_list(val.sigalgs_list) .cipher_list(val.cipher_list) .min_tls_version(TlsVersion::TLS_1_2) @@ -240,45 +279,19 @@ mod tls { .permute_extensions(val.permute_extensions) .pre_shared_key(val.pre_shared_key) .enable_ech_grease(val.enable_ech_grease) - .alps_protos(val.alps_protos) + .alps_protocols([val.alps_protos]) .alps_use_new_codepoint(val.alps_use_new_codepoint) - .cert_compression_algorithm(CERT_COMPRESSION_ALGORITHM) + .certificate_compression_algorithms(CERT_COMPRESSION_ALGORITHM) .build() } } - - impl From for Option { - #[inline(always)] - fn from(val: ChromeTlsConfig) -> Self { - Some(val.into()) - } - } -} - -mod http2 { - use super::http2_imports::*; - - pub const HEADER_PRIORITY: (u32, u8, bool) = (0, 255, true); - - pub const HEADERS_PSEUDO_ORDER: [PseudoOrder; 4] = [Method, Authority, Scheme, Path]; - - pub const SETTINGS_ORDER: [SettingsOrder; 8] = [ - HeaderTableSize, - EnablePush, - MaxConcurrentStreams, - InitialWindowSize, - MaxFrameSize, - MaxHeaderListSize, - UnknownSetting8, - UnknownSetting9, - ]; } macro_rules! mod_generator { ( $mod_name:ident, - $tls_config:expr, - $http2_config:expr, + $tls_options:expr, + $http2_options:expr, $header_initializer:ident, [($default_os:ident, $default_sec_ch_ua:tt, $default_ua:tt) $(, ($other_os:ident, $other_sec_ch_ua:tt, $other_ua:tt))*] ) => { @@ -286,7 +299,7 @@ macro_rules! mod_generator { use super::*; #[inline(always)] - pub fn emulation(option: EmulationOption) -> EmulationProvider { + pub fn emulation(option: EmulationOption) -> Emulation { let default_headers = if !option.skip_headers { #[allow(unreachable_patterns)] let default_headers = match option.emulation_os { @@ -315,12 +328,18 @@ macro_rules! mod_generator { pub fn build_emulation( option: EmulationOption, default_headers: Option - ) -> EmulationProvider { - EmulationProvider::builder() - .tls_config($tls_config) - .http2_config(conditional_http2!(option.skip_http2, $http2_config)) - .default_headers(default_headers) - .build() + ) -> Emulation { + let mut builder = Emulation::builder().tls_options($tls_options); + + if !option.skip_http2 { + builder = builder.http2_options($http2_options); + } + + if let Some(headers) = default_headers { + builder = builder.headers(headers); + } + + builder.build() } } }; @@ -334,7 +353,7 @@ macro_rules! mod_generator { use super::*; #[inline(always)] - pub fn emulation(option: EmulationOption) -> EmulationProvider { + pub fn emulation(option: EmulationOption) -> Emulation { let default_headers = if !option.skip_headers { #[allow(unreachable_patterns)] let default_headers = match option.emulation_os { @@ -364,8 +383,8 @@ macro_rules! mod_generator { mod_generator!( v100, - tls_config!(1), - http2_config!(1), + tls_options!(1), + http2_options!(1), header_initializer, [ ( @@ -499,8 +518,8 @@ mod_generator!( mod_generator!( v105, - tls_config!(2), - http2_config!(1), + tls_options!(2), + http2_options!(1), header_initializer, [ ( @@ -533,8 +552,8 @@ mod_generator!( mod_generator!( v106, - tls_config!(3), - http2_config!(2), + tls_options!(3), + http2_options!(2), header_initializer, [ ( @@ -727,8 +746,8 @@ mod_generator!( mod_generator!( v116, - tls_config!(4), - http2_config!(2), + tls_options!(4), + http2_options!(2), header_initializer, [ ( @@ -761,8 +780,8 @@ mod_generator!( mod_generator!( v117, - tls_config!(5), - http2_config!(3), + tls_options!(5), + http2_options!(3), header_initializer, [ ( @@ -890,8 +909,8 @@ mod_generator!( mod_generator!( v118, - tls_config!(4), - http2_config!(3), + tls_options!(4), + http2_options!(3), header_initializer, [ ( @@ -957,8 +976,8 @@ mod_generator!( mod_generator!( v124, - tls_config!(6, CURVES_2), - http2_config!(3), + tls_options!(6, CURVES_2), + http2_options!(3), header_initializer_with_zstd, [ ( @@ -1190,8 +1209,8 @@ mod_generator!( mod_generator!( v131, - tls_config!(6, CURVES_3), - http2_config!(3), + tls_options!(6, CURVES_3), + http2_options!(3), header_initializer_with_zstd_priority, [ ( @@ -1258,8 +1277,8 @@ mod_generator!( mod_generator!( v132, - tls_config!(7, CURVES_3), - http2_config!(3), + tls_options!(7, CURVES_3), + http2_options!(3), header_initializer_with_zstd_priority, [ ( diff --git a/src/emulation/device/firefox.rs b/src/emulation/device/firefox.rs index 2c030c0..2cbcc74 100644 --- a/src/emulation/device/firefox.rs +++ b/src/emulation/device/firefox.rs @@ -1,174 +1,253 @@ -use super::emulation_imports::*; -use super::*; -use http2::*; +use header::*; use tls::*; -macro_rules! tls_config { +use super::{emulation_imports::*, http2_imports::*, *}; + +macro_rules! headers_stream_dependency { + (1) => { + StreamDependency::new(StreamId::zero(), 41, false) + }; + (2) => { + StreamDependency::new(StreamId::from(13), 41, false) + }; +} + +macro_rules! pseudo_order { + () => { + PseudoOrder::builder() + .extend([ + PseudoId::Method, + PseudoId::Path, + PseudoId::Authority, + PseudoId::Scheme, + ]) + .build() + }; +} + +macro_rules! settings_order { + () => { + SettingsOrder::builder() + .extend([ + SettingId::HeaderTableSize, + SettingId::EnablePush, + SettingId::MaxConcurrentStreams, + SettingId::InitialWindowSize, + SettingId::MaxFrameSize, + SettingId::MaxHeaderListSize, + SettingId::EnableConnectProtocol, + SettingId::NoRfc7540Priorities, + ]) + .build() + }; +} + +macro_rules! http2_options { + (1) => { + Http2Options::builder() + .initial_stream_id(3) + .header_table_size(65536) + .enable_push(false) + .initial_window_size(131072) + .max_frame_size(16384) + .initial_connection_window_size(12517377 + 65535) + .headers_stream_dependency(headers_stream_dependency!(1)) + .headers_pseudo_order(pseudo_order!()) + .settings_order(settings_order!()) + .build() + }; + (2) => { + Http2Options::builder() + .initial_stream_id(15) + .header_table_size(65536) + .initial_window_size(131072) + .max_frame_size(16384) + .initial_connection_window_size(12517377 + 65535) + .headers_stream_dependency(headers_stream_dependency!(2)) + .headers_pseudo_order(pseudo_order!()) + .settings_order(settings_order!()) + .priorities( + Priorities::builder() + .extend([ + Priority::new( + StreamId::from(3), + StreamDependency::new(StreamId::zero(), 200, false), + ), + Priority::new( + StreamId::from(5), + StreamDependency::new(StreamId::zero(), 100, false), + ), + Priority::new( + StreamId::from(7), + StreamDependency::new(StreamId::zero(), 0, false), + ), + Priority::new( + StreamId::from(9), + StreamDependency::new(StreamId::from(7), 0, false), + ), + Priority::new( + StreamId::from(11), + StreamDependency::new(StreamId::from(3), 0, false), + ), + Priority::new( + StreamId::from(13), + StreamDependency::new(StreamId::zero(), 240, false), + ), + ]) + .build(), + ) + .build() + }; + (3) => { + Http2Options::builder() + .initial_stream_id(3) + .header_table_size(65536) + .enable_push(false) + .max_concurrent_streams(0) + .initial_window_size(131072) + .max_frame_size(16384) + .initial_connection_window_size(12517377 + 65535) + .headers_stream_dependency(headers_stream_dependency!(1)) + .headers_pseudo_order(pseudo_order!()) + .settings_order(settings_order!()) + .build() + }; + (4) => { + Http2Options::builder() + .initial_stream_id(3) + .header_table_size(4096) + .enable_push(false) + .initial_window_size(32768) + .max_frame_size(16384) + .initial_connection_window_size(12517377 + 65535) + .headers_stream_dependency(headers_stream_dependency!(1)) + .headers_pseudo_order(pseudo_order!()) + .settings_order(settings_order!()) + .build() + }; +} + +macro_rules! tls_options { (1, $cipher_list:expr, $curves:expr) => { FirefoxTlsConfig::builder() .cipher_list($cipher_list) - .curves($curves) + .curves_list($curves) .enable_ech_grease(true) .pre_shared_key(true) .psk_skip_session_tickets(true) .key_shares_limit(3) - .cert_compression_algorithm(CERT_COMPRESSION_ALGORITHM) + .certificate_compression_algorithms(CERT_COMPRESSION_ALGORITHM) .build() + .into() }; (2, $cipher_list:expr, $curves:expr) => { FirefoxTlsConfig::builder() .cipher_list($cipher_list) - .curves($curves) + .curves_list($curves) .key_shares_limit(2) .build() + .into() }; (3, $cipher_list:expr, $curves:expr) => { FirefoxTlsConfig::builder() .cipher_list($cipher_list) - .curves($curves) + .curves_list($curves) .session_ticket(false) .enable_ech_grease(true) .psk_dhe_ke(false) .key_shares_limit(2) .build() + .into() }; (4, $cipher_list:expr, $curves:expr) => { FirefoxTlsConfig::builder() .cipher_list($cipher_list) - .curves($curves) + .curves_list($curves) .enable_ech_grease(true) .enable_signed_cert_timestamps(true) .session_ticket(true) .pre_shared_key(true) .psk_skip_session_tickets(true) .key_shares_limit(3) - .cert_compression_algorithm(CERT_COMPRESSION_ALGORITHM) + .certificate_compression_algorithms(CERT_COMPRESSION_ALGORITHM) .build() + .into() }; (5, $cipher_list:expr, $curves:expr) => { FirefoxTlsConfig::builder() .cipher_list($cipher_list) - .curves($curves) + .curves_list($curves) .enable_ech_grease(true) .pre_shared_key(true) .psk_skip_session_tickets(true) .key_shares_limit(2) - .cert_compression_algorithm(CERT_COMPRESSION_ALGORITHM) + .certificate_compression_algorithms(CERT_COMPRESSION_ALGORITHM) .build() + .into() }; (6, $cipher_list:expr, $curves:expr) => { FirefoxTlsConfig::builder() .cipher_list($cipher_list) - .curves($curves) + .curves_list($curves) .enable_ech_grease(true) .enable_signed_cert_timestamps(true) .session_ticket(false) .psk_dhe_ke(false) .key_shares_limit(3) - .cert_compression_algorithm(CERT_COMPRESSION_ALGORITHM) + .certificate_compression_algorithms(CERT_COMPRESSION_ALGORITHM) .build() + .into() }; } -macro_rules! http2_config { - (1) => { - Http2Config::builder() - .initial_stream_id(3) - .header_table_size(65536) - .enable_push(false) - .initial_stream_window_size(131072) - .max_frame_size(16384) - .initial_connection_window_size(12517377 + 65535) - .headers_priority(HEADER_PRIORITY) - .headers_pseudo_order(HEADERS_PSEUDO_ORDER) - .settings_order(SETTINGS_ORDER) - .build() - }; - (2) => { - Http2Config::builder() - .initial_stream_id(15) - .header_table_size(65536) - .initial_stream_window_size(131072) - .max_frame_size(16384) - .initial_connection_window_size(12517377 + 65535) - .headers_priority((13, 41, false)) - .headers_pseudo_order(HEADERS_PSEUDO_ORDER) - .settings_order(SETTINGS_ORDER) - .priority(PRIORITY.as_slice()) - .build() - }; - (3) => { - Http2Config::builder() - .initial_stream_id(3) - .header_table_size(65536) - .enable_push(false) - .max_concurrent_streams(0) - .initial_stream_window_size(131072) - .max_frame_size(16384) - .initial_connection_window_size(12517377 + 65535) - .headers_priority(HEADER_PRIORITY) - .headers_pseudo_order(HEADERS_PSEUDO_ORDER) - .settings_order(SETTINGS_ORDER) - .build() - }; - (4) => { - Http2Config::builder() - .initial_stream_id(3) - .header_table_size(4096) - .enable_push(false) - .initial_stream_window_size(32768) - .max_frame_size(16384) - .initial_connection_window_size(12517377 + 65535) - .headers_priority(HEADER_PRIORITY) - .headers_pseudo_order(HEADERS_PSEUDO_ORDER) - .settings_order(SETTINGS_ORDER) - .build() - }; -} +mod header { + use super::*; -#[inline] -fn header_initializer(ua: &'static str) -> HeaderMap { - let mut headers = HeaderMap::new(); - header_firefox_ua!(headers, ua); - header_firefox_accept!(headers); - header_firefox_sec_fetch!(headers); - headers -} + #[inline] + pub fn header_initializer(ua: &'static str) -> HeaderMap { + let mut headers = HeaderMap::new(); + header_firefox_ua!(headers, ua); + header_firefox_accept!(headers); + header_firefox_sec_fetch!(headers); + headers + } -#[inline] -fn header_initializer_with_zstd(ua: &'static str) -> HeaderMap { - let mut headers = HeaderMap::new(); - header_firefox_ua!(headers, ua); - header_firefox_accept!(zstd, headers); - header_firefox_sec_fetch!(headers); - headers.insert( - HeaderName::from_static("priority"), - HeaderValue::from_static("u=0, i"), - ); - headers + #[inline] + pub fn header_initializer_with_zstd(ua: &'static str) -> HeaderMap { + let mut headers = HeaderMap::new(); + header_firefox_ua!(headers, ua); + header_firefox_accept!(zstd, headers); + header_firefox_sec_fetch!(headers); + headers.insert( + HeaderName::from_static("priority"), + HeaderValue::from_static("u=0, i"), + ); + headers + } } mod tls { use super::tls_imports::*; - pub const CURVES_1: &[SslCurve] = &[ - SslCurve::X25519, - SslCurve::SECP256R1, - SslCurve::SECP384R1, - SslCurve::SECP521R1, - SslCurve::FFDHE2048, - SslCurve::FFDHE3072, - ]; - - pub const CURVES_2: &[SslCurve] = &[ - SslCurve::X25519_MLKEM768, - SslCurve::X25519, - SslCurve::SECP256R1, - SslCurve::SECP384R1, - SslCurve::SECP521R1, - SslCurve::FFDHE2048, - SslCurve::FFDHE3072, - ]; + pub const CURVES_1: &str = join!( + ":", + "X25519", + "P-256", + "P-384", + "P-521", + "ffdhe2048", + "ffdhe3072" + ); + pub const CURVES_2: &str = join!( + ":", + "X25519MLKEM768", + "X25519", + "P-256", + "P-384", + "P-521", + "ffdhe2048", + "ffdhe3072" + ); pub const CIPHER_LIST_1: &str = join!( ":", @@ -190,7 +269,6 @@ mod tls { "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA" ); - pub const CIPHER_LIST_2: &str = join!( ":", "TLS_AES_128_GCM_SHA256", @@ -225,10 +303,10 @@ mod tls { "rsa_pkcs1_sha1" ); - pub const CERT_COMPRESSION_ALGORITHM: &[CertCompressionAlgorithm] = &[ - CertCompressionAlgorithm::Zlib, - CertCompressionAlgorithm::Brotli, - CertCompressionAlgorithm::Zstd, + pub const CERT_COMPRESSION_ALGORITHM: &[CertificateCompressionAlgorithm] = &[ + CertificateCompressionAlgorithm::ZLIB, + CertificateCompressionAlgorithm::BROTLI, + CertificateCompressionAlgorithm::ZSTD, ]; pub const DELEGATED_CREDENTIALS: &str = join!( @@ -241,38 +319,25 @@ mod tls { pub const RECORD_SIZE_LIMIT: u16 = 0x4001; - pub const EXTENSION_PERMUTATION_INDICES: &[u8] = &{ - const EXTENSIONS: &[ExtensionType] = &[ - ExtensionType::SERVER_NAME, - ExtensionType::EXTENDED_MASTER_SECRET, - ExtensionType::RENEGOTIATE, - ExtensionType::SUPPORTED_GROUPS, - ExtensionType::EC_POINT_FORMATS, - ExtensionType::SESSION_TICKET, - ExtensionType::APPLICATION_LAYER_PROTOCOL_NEGOTIATION, - ExtensionType::STATUS_REQUEST, - ExtensionType::DELEGATED_CREDENTIAL, - ExtensionType::CERTIFICATE_TIMESTAMP, - ExtensionType::KEY_SHARE, - ExtensionType::SUPPORTED_VERSIONS, - ExtensionType::SIGNATURE_ALGORITHMS, - ExtensionType::PSK_KEY_EXCHANGE_MODES, - ExtensionType::RECORD_SIZE_LIMIT, - ExtensionType::CERT_COMPRESSION, - ExtensionType::ENCRYPTED_CLIENT_HELLO, - ]; - - let mut indices = [0u8; EXTENSIONS.len()]; - let mut index = usize::MIN; - while index < EXTENSIONS.len() { - if let Some(idx) = ExtensionType::index_of(EXTENSIONS[index]) { - indices[index] = idx as u8; - } - index += 1; - } - - indices - }; + pub const EXTENSION_PERMUTATION_INDICES: &[ExtensionType] = &[ + ExtensionType::SERVER_NAME, + ExtensionType::EXTENDED_MASTER_SECRET, + ExtensionType::RENEGOTIATE, + ExtensionType::SUPPORTED_GROUPS, + ExtensionType::EC_POINT_FORMATS, + ExtensionType::SESSION_TICKET, + ExtensionType::APPLICATION_LAYER_PROTOCOL_NEGOTIATION, + ExtensionType::STATUS_REQUEST, + ExtensionType::DELEGATED_CREDENTIAL, + ExtensionType::CERTIFICATE_TIMESTAMP, + ExtensionType::KEY_SHARE, + ExtensionType::SUPPORTED_VERSIONS, + ExtensionType::SIGNATURE_ALGORITHMS, + ExtensionType::PSK_KEY_EXCHANGE_MODES, + ExtensionType::RECORD_SIZE_LIMIT, + ExtensionType::CERT_COMPRESSION, + ExtensionType::ENCRYPTED_CLIENT_HELLO, + ]; #[derive(TypedBuilder)] pub struct FirefoxTlsConfig { @@ -283,7 +348,7 @@ mod tls { cipher_list: &'static str, #[builder(setter(into))] - curves: &'static [SslCurve], + curves_list: &'static str, #[builder(default = true)] session_ticket: bool, @@ -313,16 +378,16 @@ mod tls { psk_dhe_ke: bool, #[builder(default, setter(into))] - cert_compression_algorithm: Option<&'static [CertCompressionAlgorithm]>, + certificate_compression_algorithms: Option<&'static [CertificateCompressionAlgorithm]>, #[builder(default = EXTENSION_PERMUTATION_INDICES, setter(into))] - extension_permutation_indices: &'static [u8], + extension_permutation: &'static [ExtensionType], } - impl From for TlsConfig { + impl From for TlsOptions { fn from(val: FirefoxTlsConfig) -> Self { - TlsConfig::builder() - .curves(val.curves) + let mut builder = TlsOptions::builder() + .curves_list(val.curves_list) .sigalgs_list(val.sigalgs_list) .cipher_list(val.cipher_list) .session_ticket(val.session_ticket) @@ -331,82 +396,31 @@ mod tls { .enable_ocsp_stapling(true) .enable_ech_grease(val.enable_ech_grease) .enable_signed_cert_timestamps(val.enable_signed_cert_timestamps) - .alpn_protos(AlpnProtos::ALL) + .alpn_protocols([AlpnProtocol::HTTP2, AlpnProtocol::HTTP1]) .min_tls_version(TlsVersion::TLS_1_2) .max_tls_version(TlsVersion::TLS_1_3) .key_shares_limit(val.key_shares_limit) .pre_shared_key(val.pre_shared_key) .psk_skip_session_ticket(val.psk_skip_session_tickets) .psk_dhe_ke(val.psk_dhe_ke) - .cert_compression_algorithm(val.cert_compression_algorithm) - .extension_permutation_indices(val.extension_permutation_indices) + .extension_permutation(val.extension_permutation) .aes_hw_override(true) - .random_aes_hw_override(true) - .build() - } - } + .random_aes_hw_override(true); - impl From for Option { - #[inline(always)] - fn from(val: FirefoxTlsConfig) -> Self { - Some(val.into()) + if let Some(cert_compression_algorithms) = val.certificate_compression_algorithms { + builder = builder.certificate_compression_algorithms(cert_compression_algorithms) + } + + builder.build() } } } -mod http2 { - use super::http2_imports::*; - - pub const HEADER_PRIORITY: (u32, u8, bool) = (0, 41, false); - - pub const HEADERS_PSEUDO_ORDER: [PseudoOrder; 4] = [Method, Path, Authority, Scheme]; - - pub const SETTINGS_ORDER: [SettingsOrder; 8] = [ - HeaderTableSize, - EnablePush, - MaxConcurrentStreams, - InitialWindowSize, - MaxFrameSize, - MaxHeaderListSize, - UnknownSetting8, - UnknownSetting9, - ]; - - pub static PRIORITY: LazyLock<[Priority; 6]> = LazyLock::new(|| { - [ - Priority::new( - StreamId::from(3), - StreamDependency::new(StreamId::zero(), 200, false), - ), - Priority::new( - StreamId::from(5), - StreamDependency::new(StreamId::zero(), 100, false), - ), - Priority::new( - StreamId::from(7), - StreamDependency::new(StreamId::zero(), 0, false), - ), - Priority::new( - StreamId::from(9), - StreamDependency::new(StreamId::from(7), 0, false), - ), - Priority::new( - StreamId::from(11), - StreamDependency::new(StreamId::from(3), 0, false), - ), - Priority::new( - StreamId::from(13), - StreamDependency::new(StreamId::zero(), 240, false), - ), - ] - }); -} - macro_rules! mod_generator { ( $mod_name:ident, - $tls_config:expr, - $http2_config:expr, + $tls_options:expr, + $http2_options:expr, $header_initializer:ident, [($default_os:ident, $default_ua:tt) $(, ($other_os:ident, $other_ua:tt))*] ) => { @@ -414,7 +428,7 @@ macro_rules! mod_generator { use super::*; #[inline(always)] - pub fn emulation(option: EmulationOption) -> EmulationProvider { + pub fn emulation(option: EmulationOption) -> Emulation { let default_headers = if !option.skip_headers { #[allow(unreachable_patterns)] let default_headers = match option.emulation_os { @@ -440,12 +454,18 @@ macro_rules! mod_generator { pub fn build_emulation( option: EmulationOption, default_headers: Option - ) -> EmulationProvider { - EmulationProvider::builder() - .tls_config($tls_config) - .http2_config(conditional_http2!(option.skip_http2, $http2_config)) - .default_headers(default_headers) - .build() + ) -> Emulation { + let mut builder = Emulation::builder().tls_options($tls_options); + + if !option.skip_http2 { + builder = builder.http2_options($http2_options); + } + + if let Some(headers) = default_headers { + builder = builder.headers(headers); + } + + builder.build() } } }; @@ -459,7 +479,7 @@ macro_rules! mod_generator { use super::*; #[inline(always)] - pub fn emulation(option: EmulationOption) -> EmulationProvider { + pub fn emulation(option: EmulationOption) -> Emulation { let default_headers = if !option.skip_headers { #[allow(unreachable_patterns)] let default_headers = match option.emulation_os { @@ -486,8 +506,8 @@ macro_rules! mod_generator { mod_generator!( ff109, - tls_config!(2, CIPHER_LIST_1, CURVES_1), - http2_config!(2), + tls_options!(2, CIPHER_LIST_1, CURVES_1), + http2_options!(2), header_initializer, [ ( @@ -543,8 +563,8 @@ mod_generator!( mod_generator!( ff128, - tls_config!(3, CIPHER_LIST_2, CURVES_1), - http2_config!(3), + tls_options!(3, CIPHER_LIST_2, CURVES_1), + http2_options!(3), header_initializer_with_zstd, [ ( @@ -572,8 +592,8 @@ mod_generator!( mod_generator!( ff133, - tls_config!(1, CIPHER_LIST_1, CURVES_2), - http2_config!(1), + tls_options!(1, CIPHER_LIST_1, CURVES_2), + http2_options!(1), header_initializer_with_zstd, [ ( @@ -601,8 +621,8 @@ mod_generator!( mod_generator!( ff135, - tls_config!(4, CIPHER_LIST_1, CURVES_2), - http2_config!(1), + tls_options!(4, CIPHER_LIST_1, CURVES_2), + http2_options!(1), header_initializer_with_zstd, [ ( @@ -622,8 +642,8 @@ mod_generator!( mod_generator!( ff_private_135, - tls_config!(6, CIPHER_LIST_1, CURVES_2), - http2_config!(1), + tls_options!(6, CIPHER_LIST_1, CURVES_2), + http2_options!(1), header_initializer_with_zstd, [ ( @@ -643,8 +663,8 @@ mod_generator!( mod_generator!( ff_android_135, - tls_config!(5, CIPHER_LIST_1, CURVES_1), - http2_config!(4), + tls_options!(5, CIPHER_LIST_1, CURVES_1), + http2_options!(4), header_initializer_with_zstd, [( Android, diff --git a/src/emulation/device/macros.rs b/src/emulation/device/macros.rs index 57dc7bd..4e73231 100644 --- a/src/emulation/device/macros.rs +++ b/src/emulation/device/macros.rs @@ -1,9 +1,3 @@ -macro_rules! conditional_http2 { - ($skip_http2:expr, $http2:expr) => { - if $skip_http2 { None } else { Some($http2) } - }; -} - macro_rules! header_chrome_sec_ch_ua { ($headers:expr, $ua:expr, $platform:expr, $is_mobile:expr) => { let mobile = if $is_mobile { "?1" } else { "?0" }; diff --git a/src/emulation/device/mod.rs b/src/emulation/device/mod.rs index e770d3f..b697a3a 100644 --- a/src/emulation/device/mod.rs +++ b/src/emulation/device/mod.rs @@ -11,26 +11,28 @@ pub mod opera; pub mod safari; mod emulation_imports { - pub use crate::emulation::{EmulationOS, EmulationOption}; #[cfg(all(feature = "gzip", feature = "deflate", feature = "brotli"))] pub use wreq::header::ACCEPT_ENCODING; - pub use wreq::header::{ - ACCEPT, ACCEPT_LANGUAGE, HeaderMap, HeaderName, HeaderValue, USER_AGENT, + pub use wreq::{ + Emulation, + header::{ACCEPT, ACCEPT_LANGUAGE, HeaderMap, HeaderName, HeaderValue, USER_AGENT}, + http2::Http2Options, }; - pub use wreq::{EmulationProvider, Http2Config}; + + pub use crate::emulation::{EmulationOS, EmulationOption}; } mod tls_imports { pub use typed_builder::TypedBuilder; - pub use wreq::{ - AlpnProtos, AlpsProtos, CertCompressionAlgorithm, ExtensionType, SslCurve, TlsConfig, + pub use wreq::tls::{ + AlpnProtocol, AlpsProtocol, CertificateCompressionAlgorithm, ExtensionType, TlsOptions, TlsVersion, }; } mod http2_imports { - pub use std::sync::LazyLock; - pub use wreq::PseudoOrder::{self, *}; - pub use wreq::SettingsOrder::{self, *}; - pub use wreq::{Priority, StreamDependency, StreamId}; + pub use wreq::http2::{ + Priorities, Priority, PseudoId, PseudoOrder, SettingId, SettingsOrder, StreamDependency, + StreamId, + }; } diff --git a/src/emulation/device/okhttp.rs b/src/emulation/device/okhttp.rs index 1fb3549..f2c6bb5 100644 --- a/src/emulation/device/okhttp.rs +++ b/src/emulation/device/okhttp.rs @@ -1,134 +1,131 @@ -use super::emulation_imports::*; -use super::*; -use http2::*; -use tls::*; +use super::{emulation_imports::*, http2_imports::*, tls_imports::*}; -#[inline] -pub fn build_emulation( +const CURVES: &str = join!(":", "X25519", "P-256", "P-384"); + +const SIGALGS_LIST: &str = join!( + ":", + "ecdsa_secp256r1_sha256", + "rsa_pss_rsae_sha256", + "rsa_pkcs1_sha256", + "ecdsa_secp384r1_sha384", + "rsa_pss_rsae_sha384", + "rsa_pkcs1_sha384", + "rsa_pss_rsae_sha512", + "rsa_pkcs1_sha512", + "rsa_pkcs1_sha1" +); + +const CIPHER_LIST: &str = join!( + ":", + "TLS_AES_128_GCM_SHA256", + "TLS_AES_256_GCM_SHA384", + "TLS_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_3DES_EDE_CBC_SHA" +); + +fn build_emulation( option: EmulationOption, cipher_list: &'static str, default_headers: Option, -) -> EmulationProvider { - EmulationProvider::builder() - .tls_config(OkHttpTlsConfig::builder().cipher_list(cipher_list).build()) - .http2_config(conditional_http2!( - option.skip_http2, - Http2Config::builder() - .initial_stream_window_size(6291456) - .initial_connection_window_size(15728640) - .max_concurrent_streams(1000) - .max_header_list_size(262144) - .header_table_size(65536) - .headers_priority(HEADER_PRIORITY) - .headers_pseudo_order(HEADERS_PSEUDO_ORDER) - .settings_order(SETTINGS_ORDER) - .build() - )) - .default_headers(default_headers) +) -> Emulation { + let tls_opts = OkHttpTlsConfig::builder() + .cipher_list(cipher_list) .build() -} + .into(); -#[inline] -fn header_initializer(ua: &'static str) -> HeaderMap { - let mut headers = HeaderMap::new(); - headers.insert(ACCEPT, HeaderValue::from_static("*/*")); - headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.9")); - headers.insert(USER_AGENT, HeaderValue::from_static(ua)); - #[cfg(all(feature = "gzip", feature = "deflate", feature = "brotli"))] - headers.insert( - ACCEPT_ENCODING, - HeaderValue::from_static("gzip, deflate, br"), - ); - headers -} + let mut builder = Emulation::builder().tls_options(tls_opts); -mod tls { - use super::tls_imports::*; + if !option.skip_http2 { + let settings_order = SettingsOrder::builder() + .extend([ + SettingId::HeaderTableSize, + SettingId::EnablePush, + SettingId::MaxConcurrentStreams, + SettingId::InitialWindowSize, + SettingId::MaxFrameSize, + SettingId::MaxHeaderListSize, + SettingId::EnableConnectProtocol, + SettingId::NoRfc7540Priorities, + ]) + .build(); - pub const CURVES: &[SslCurve] = &[SslCurve::X25519, SslCurve::SECP256R1, SslCurve::SECP384R1]; - - pub const SIGALGS_LIST: &str = join!( - ":", - "ecdsa_secp256r1_sha256", - "rsa_pss_rsae_sha256", - "rsa_pkcs1_sha256", - "ecdsa_secp384r1_sha384", - "rsa_pss_rsae_sha384", - "rsa_pkcs1_sha384", - "rsa_pss_rsae_sha512", - "rsa_pkcs1_sha512", - "rsa_pkcs1_sha1" - ); + let http2_opts = Http2Options::builder() + .initial_window_size(6291456) + .initial_connection_window_size(15728640) + .max_concurrent_streams(1000) + .max_header_list_size(262144) + .header_table_size(65536) + .headers_stream_dependency(StreamDependency::new(StreamId::zero(), 255, true)) + .headers_pseudo_order( + PseudoOrder::builder() + .extend([ + PseudoId::Method, + PseudoId::Path, + PseudoId::Authority, + PseudoId::Scheme, + ]) + .build(), + ) + .settings_order(settings_order) + .build(); - pub const CIPHER_LIST: &str = join!( - ":", - "TLS_AES_128_GCM_SHA256", - "TLS_AES_256_GCM_SHA384", - "TLS_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "TLS_RSA_WITH_AES_128_GCM_SHA256", - "TLS_RSA_WITH_AES_256_GCM_SHA384", - "TLS_RSA_WITH_AES_128_CBC_SHA", - "TLS_RSA_WITH_AES_256_CBC_SHA", - "TLS_RSA_WITH_3DES_EDE_CBC_SHA" - ); - - #[derive(TypedBuilder)] - pub struct OkHttpTlsConfig { - #[builder(default = CURVES)] - curves: &'static [SslCurve], - - #[builder(default = SIGALGS_LIST)] - sigalgs_list: &'static str, - - cipher_list: &'static str, + builder = builder.http2_options(http2_opts); } - impl From for TlsConfig { - fn from(val: OkHttpTlsConfig) -> Self { - TlsConfig::builder() - .enable_ocsp_stapling(true) - .curves(val.curves) - .sigalgs_list(val.sigalgs_list) - .cipher_list(val.cipher_list) - .min_tls_version(TlsVersion::TLS_1_2) - .max_tls_version(TlsVersion::TLS_1_3) - .build() - } + if let Some(headers) = default_headers { + builder = builder.headers(headers); } - impl From for Option { - #[inline(always)] - fn from(val: OkHttpTlsConfig) -> Self { - Some(val.into()) - } - } + builder.build() } -mod http2 { - use super::http2_imports::*; +#[derive(TypedBuilder)] +struct OkHttpTlsConfig { + #[builder(default = CURVES)] + curves: &'static str, + + #[builder(default = SIGALGS_LIST)] + sigalgs_list: &'static str, - pub const HEADER_PRIORITY: (u32, u8, bool) = (0, 255, true); + cipher_list: &'static str, +} - pub const HEADERS_PSEUDO_ORDER: [PseudoOrder; 4] = [Method, Path, Authority, Scheme]; +impl From for TlsOptions { + fn from(val: OkHttpTlsConfig) -> Self { + TlsOptions::builder() + .enable_ocsp_stapling(true) + .curves_list(val.curves) + .sigalgs_list(val.sigalgs_list) + .cipher_list(val.cipher_list) + .min_tls_version(TlsVersion::TLS_1_2) + .max_tls_version(TlsVersion::TLS_1_3) + .build() + } +} - pub const SETTINGS_ORDER: [SettingsOrder; 8] = [ - HeaderTableSize, - EnablePush, - MaxConcurrentStreams, - InitialWindowSize, - MaxFrameSize, - MaxHeaderListSize, - UnknownSetting8, - UnknownSetting9, - ]; +fn header_initializer(ua: &'static str) -> HeaderMap { + let mut headers = HeaderMap::new(); + headers.insert(ACCEPT, HeaderValue::from_static("*/*")); + headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.9")); + headers.insert(USER_AGENT, HeaderValue::from_static(ua)); + #[cfg(all(feature = "gzip", feature = "deflate", feature = "brotli"))] + headers.insert( + ACCEPT_ENCODING, + HeaderValue::from_static("gzip, deflate, br"), + ); + headers } macro_rules! mod_generator { @@ -137,7 +134,7 @@ macro_rules! mod_generator { use super::*; #[inline(always)] - pub fn emulation(option: EmulationOption) -> EmulationProvider { + pub fn emulation(option: EmulationOption) -> Emulation { let default_headers = if !option.skip_headers { Some(header_initializer($ua)) } else { diff --git a/src/emulation/device/opera.rs b/src/emulation/device/opera.rs index 0bf12ab..df69829 100644 --- a/src/emulation/device/opera.rs +++ b/src/emulation/device/opera.rs @@ -1,77 +1,112 @@ -use super::emulation_imports::*; -use super::*; -use http2::*; +use header::*; use tls::*; -macro_rules! tls_config { - (6, $curves:expr) => { +use super::{emulation_imports::*, http2_imports::*, *}; + +macro_rules! headers_stream_dependency { + () => { + StreamDependency::new(StreamId::zero(), 255, true) + }; +} + +macro_rules! pseudo_order { + () => { + PseudoOrder::builder() + .extend([ + PseudoId::Method, + PseudoId::Authority, + PseudoId::Scheme, + PseudoId::Path, + ]) + .build() + }; +} + +macro_rules! settings_order { + () => { + SettingsOrder::builder() + .extend([ + SettingId::HeaderTableSize, + SettingId::EnablePush, + SettingId::MaxConcurrentStreams, + SettingId::InitialWindowSize, + SettingId::MaxFrameSize, + SettingId::MaxHeaderListSize, + SettingId::EnableConnectProtocol, + SettingId::NoRfc7540Priorities, + ]) + .build() + }; +} + +macro_rules! tls_options { + ($curves:expr) => { OperaTlsConfig::builder() .curves($curves) .permute_extensions(true) .pre_shared_key(true) .enable_ech_grease(true) .build() + .into() }; } -macro_rules! http2_config { - (3) => { - Http2Config::builder() - .initial_stream_window_size(6291456) +macro_rules! http2_options { + () => { + Http2Options::builder() + .initial_window_size(6291456) .initial_connection_window_size(15728640) .max_header_list_size(262144) .header_table_size(65536) .enable_push(false) - .headers_priority(HEADER_PRIORITY) - .headers_pseudo_order(HEADERS_PSEUDO_ORDER) - .settings_order(SETTINGS_ORDER) + .headers_stream_dependency(headers_stream_dependency!()) + .headers_pseudo_order(pseudo_order!()) + .settings_order(settings_order!()) .build() }; } -#[inline] -fn header_initializer_with_zstd_priority( - sec_ch_ua: &'static str, - ua: &'static str, - emulation_os: EmulationOS, -) -> HeaderMap { - let mut headers = HeaderMap::new(); - header_chrome_sec_ch_ua!( - headers, - sec_ch_ua, - emulation_os.platform(), - emulation_os.is_mobile() - ); - header_chrome_ua!(headers, ua); - - headers.insert(ACCEPT, HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")); - #[cfg(all( - feature = "gzip", - feature = "deflate", - feature = "brotli", - feature = "zstd" - ))] - headers.insert( - ACCEPT_ENCODING, - HeaderValue::from_static("gzip, deflate, br, zstd"), - ); - headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.9")); - headers.insert( - HeaderName::from_static("priority"), - HeaderValue::from_static("u=0, i"), - ); - headers +mod header { + use super::*; + + #[inline] + pub fn header_initializer_with_zstd_priority( + sec_ch_ua: &'static str, + ua: &'static str, + emulation_os: EmulationOS, + ) -> HeaderMap { + let mut headers = HeaderMap::new(); + header_chrome_sec_ch_ua!( + headers, + sec_ch_ua, + emulation_os.platform(), + emulation_os.is_mobile() + ); + header_chrome_ua!(headers, ua); + + headers.insert(ACCEPT, HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")); + #[cfg(all( + feature = "gzip", + feature = "deflate", + feature = "brotli", + feature = "zstd" + ))] + headers.insert( + ACCEPT_ENCODING, + HeaderValue::from_static("gzip, deflate, br, zstd"), + ); + headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.9")); + headers.insert( + HeaderName::from_static("priority"), + HeaderValue::from_static("u=0, i"), + ); + headers + } } - mod tls { use super::tls_imports::*; - pub const CURVES: &[SslCurve] = &[ - SslCurve::X25519_MLKEM768, - SslCurve::X25519, - SslCurve::SECP256R1, - SslCurve::SECP384R1, - ]; + pub const CURVES: &'static str = join!(":", "X25519MLKEM768", "X25519", "P-256", "P-384"); pub const CIPHER_LIST: &str = join!( ":", @@ -104,13 +139,13 @@ mod tls { "rsa_pkcs1_sha512" ); - pub const CERT_COMPRESSION_ALGORITHM: &[CertCompressionAlgorithm] = - &[CertCompressionAlgorithm::Brotli]; + pub const CERT_COMPRESSION_ALGORITHM: &[CertificateCompressionAlgorithm] = + &[CertificateCompressionAlgorithm::BROTLI]; #[derive(TypedBuilder)] pub struct OperaTlsConfig { #[builder(default = CURVES)] - curves: &'static [SslCurve], + curves: &'static str, #[builder(default = SIGALGS_LIST)] sigalgs_list: &'static str, @@ -118,8 +153,8 @@ mod tls { #[builder(default = CIPHER_LIST)] cipher_list: &'static str, - #[builder(default = AlpsProtos::HTTP2, setter(into))] - alps_protos: AlpsProtos, + #[builder(default = AlpsProtocol::HTTP2, setter(into))] + alps_protos: AlpsProtocol, #[builder(default = false)] alps_use_new_codepoint: bool, @@ -134,13 +169,13 @@ mod tls { pre_shared_key: bool, } - impl From for TlsConfig { + impl From for TlsOptions { fn from(val: OperaTlsConfig) -> Self { - TlsConfig::builder() + TlsOptions::builder() .grease_enabled(true) .enable_ocsp_stapling(true) .enable_signed_cert_timestamps(true) - .curves(val.curves) + .curves_list(val.curves) .sigalgs_list(val.sigalgs_list) .cipher_list(val.cipher_list) .min_tls_version(TlsVersion::TLS_1_2) @@ -148,45 +183,19 @@ mod tls { .permute_extensions(val.permute_extensions) .pre_shared_key(val.pre_shared_key) .enable_ech_grease(val.enable_ech_grease) - .alps_protos(val.alps_protos) + .alps_protocols([val.alps_protos]) .alps_use_new_codepoint(val.alps_use_new_codepoint) - .cert_compression_algorithm(CERT_COMPRESSION_ALGORITHM) + .certificate_compression_algorithms(CERT_COMPRESSION_ALGORITHM) .build() } } - - impl From for Option { - #[inline(always)] - fn from(val: OperaTlsConfig) -> Self { - Some(val.into()) - } - } -} - -mod http2 { - use super::http2_imports::*; - - pub const HEADER_PRIORITY: (u32, u8, bool) = (0, 255, true); - - pub const HEADERS_PSEUDO_ORDER: [PseudoOrder; 4] = [Method, Authority, Scheme, Path]; - - pub const SETTINGS_ORDER: [SettingsOrder; 8] = [ - HeaderTableSize, - EnablePush, - MaxConcurrentStreams, - InitialWindowSize, - MaxFrameSize, - MaxHeaderListSize, - UnknownSetting8, - UnknownSetting9, - ]; } macro_rules! mod_generator { ( $mod_name:ident, - $tls_config:expr, - $http2_config:expr, + $tls_options:expr, + $http2_options:expr, $header_initializer:ident, [($default_os:ident, $default_sec_ch_ua:tt, $default_ua:tt) $(, ($other_os:ident, $other_sec_ch_ua:tt, $other_ua:tt))*] ) => { @@ -194,7 +203,7 @@ macro_rules! mod_generator { use super::*; #[inline(always)] - pub fn emulation(option: EmulationOption) -> EmulationProvider { + pub fn emulation(option: EmulationOption) -> Emulation { let default_headers = if !option.skip_headers { #[allow(unreachable_patterns)] let default_headers = match option.emulation_os { @@ -223,12 +232,18 @@ macro_rules! mod_generator { pub fn build_emulation( option: EmulationOption, default_headers: Option - ) -> EmulationProvider { - EmulationProvider::builder() - .tls_config($tls_config) - .http2_config(conditional_http2!(option.skip_http2, $http2_config)) - .default_headers(default_headers) - .build() + ) -> Emulation { + let mut builder = Emulation::builder().tls_options($tls_options); + + if !option.skip_http2 { + builder = builder.http2_options($http2_options); + } + + if let Some(headers) = default_headers { + builder = builder.headers(headers); + } + + builder.build() } } }; @@ -242,7 +257,7 @@ macro_rules! mod_generator { use super::*; #[inline(always)] - pub fn emulation(option: EmulationOption) -> EmulationProvider { + pub fn emulation(option: EmulationOption) -> Emulation { let default_headers = if !option.skip_headers { #[allow(unreachable_patterns)] let default_headers = match option.emulation_os { @@ -272,8 +287,8 @@ macro_rules! mod_generator { mod_generator!( opera116, - tls_config!(6, CURVES), - http2_config!(3), + tls_options!(CURVES), + http2_options!(), header_initializer_with_zstd_priority, [ ( diff --git a/src/emulation/device/safari.rs b/src/emulation/device/safari.rs index 5759042..53f3628 100644 --- a/src/emulation/device/safari.rs +++ b/src/emulation/device/safari.rs @@ -1,157 +1,229 @@ -use super::emulation_imports::*; -use super::*; -use http2::*; +use header::*; use tls::*; -macro_rules! tls_config { +use super::{emulation_imports::*, http2_imports::*, *}; + +macro_rules! headers_stream_dependency { + (1) => { + StreamDependency::new(StreamId::zero(), 255, true) + }; + (2) => { + StreamDependency::new(StreamId::zero(), 255, false) + }; +} + +macro_rules! headers_pseudo_order { + (1) => { + PseudoOrder::builder() + .extend([ + PseudoId::Method, + PseudoId::Scheme, + PseudoId::Path, + PseudoId::Authority, + ]) + .build() + }; + (2) => { + PseudoOrder::builder() + .extend([ + PseudoId::Method, + PseudoId::Scheme, + PseudoId::Authority, + PseudoId::Path, + ]) + .build() + }; +} + +macro_rules! settings_order { + (1) => { + SettingsOrder::builder() + .extend([ + SettingId::HeaderTableSize, + SettingId::EnablePush, + SettingId::InitialWindowSize, + SettingId::MaxConcurrentStreams, + SettingId::MaxFrameSize, + SettingId::MaxHeaderListSize, + SettingId::EnableConnectProtocol, + SettingId::NoRfc7540Priorities, + ]) + .build() + }; + (2) => { + SettingsOrder::builder() + .extend([ + SettingId::HeaderTableSize, + SettingId::EnablePush, + SettingId::MaxConcurrentStreams, + SettingId::InitialWindowSize, + SettingId::MaxFrameSize, + SettingId::MaxHeaderListSize, + SettingId::EnableConnectProtocol, + SettingId::NoRfc7540Priorities, + ]) + .build() + }; +} + +macro_rules! tls_options { (1, $cipher_list:expr) => { - SafariTlsConfig::builder().cipher_list($cipher_list).build() + SafariTlsConfig::builder() + .cipher_list($cipher_list) + .build() + .into() }; (2, $cipher_list:expr, $sigalgs_list:expr) => { SafariTlsConfig::builder() .cipher_list($cipher_list) .sigalgs_list($sigalgs_list) .build() + .into() }; } -macro_rules! http2_config { +macro_rules! http2_options { (1) => { - Http2Config::builder() - .initial_stream_window_size(2097152) + Http2Options::builder() + .initial_window_size(2097152) .initial_connection_window_size(10551295) .max_concurrent_streams(100) - .headers_priority(HEADER_PRIORITY) - .headers_pseudo_order(HEADERS_PSEUDO_ORDER) - .settings_order(SETTINGS_ORDER) + .headers_stream_dependency(headers_stream_dependency!(1)) + .headers_pseudo_order(headers_pseudo_order!(1)) + .settings_order(settings_order!(1)) .build() }; (2) => { - Http2Config::builder() - .initial_stream_window_size(2097152) + Http2Options::builder() + .initial_window_size(2097152) .initial_connection_window_size(10551295) .max_concurrent_streams(100) .enable_push(false) - .headers_priority(HEADER_PRIORITY) - .headers_pseudo_order(HEADERS_PSEUDO_ORDER) - .settings_order(SETTINGS_ORDER) + .headers_stream_dependency(headers_stream_dependency!(1)) + .headers_pseudo_order(headers_pseudo_order!(1)) + .settings_order(settings_order!(1)) .build() }; (3) => { - Http2Config::builder() - .initial_stream_window_size(2097152) + Http2Options::builder() + .initial_window_size(2097152) .initial_connection_window_size(10485760) .max_concurrent_streams(100) .enable_push(false) - .unknown_setting8(true) - .unknown_setting9(true) - .headers_priority(NEW_HEADER_PRIORITY) - .headers_pseudo_order(NEW_HEADERS_PSEUDO_ORDER) - .settings_order(NEW_SETTINGS_ORDER) + .enable_connect_protocol(true) + .no_rfc7540_priorities(true) + .headers_stream_dependency(headers_stream_dependency!(2)) + .headers_pseudo_order(headers_pseudo_order!(2)) + .settings_order(settings_order!(2)) .build() }; (4) => { - Http2Config::builder() - .initial_stream_window_size(4194304) + Http2Options::builder() + .initial_window_size(4194304) .initial_connection_window_size(10551295) .max_concurrent_streams(100) - .headers_priority(HEADER_PRIORITY) - .headers_pseudo_order(HEADERS_PSEUDO_ORDER) - .settings_order(SETTINGS_ORDER) + .headers_stream_dependency(headers_stream_dependency!(1)) + .headers_pseudo_order(headers_pseudo_order!(1)) + .settings_order(settings_order!(1)) .build() }; (5) => { - Http2Config::builder() - .initial_stream_window_size(4194304) + Http2Options::builder() + .initial_window_size(4194304) .initial_connection_window_size(10551295) .max_concurrent_streams(100) .enable_push(false) - .headers_priority(HEADER_PRIORITY) - .headers_pseudo_order(HEADERS_PSEUDO_ORDER) - .settings_order(SETTINGS_ORDER) + .headers_stream_dependency(headers_stream_dependency!(1)) + .headers_pseudo_order(headers_pseudo_order!(1)) + .settings_order(settings_order!(1)) .build() }; (6) => { - Http2Config::builder() - .initial_stream_window_size(2097152) + Http2Options::builder() + .initial_window_size(2097152) .initial_connection_window_size(10485760) .max_concurrent_streams(100) .enable_push(false) - .unknown_setting9(true) - .headers_priority(NEW_HEADER_PRIORITY) - .headers_pseudo_order(NEW_HEADERS_PSEUDO_ORDER) - .settings_order(NEW_SETTINGS_ORDER) + .no_rfc7540_priorities(true) + .headers_stream_dependency(headers_stream_dependency!(2)) + .headers_pseudo_order(headers_pseudo_order!(2)) + .settings_order(settings_order!(2)) .build() }; } -#[inline] -fn header_initializer_for_15(ua: &'static str) -> HeaderMap { - let mut headers = HeaderMap::new(); - headers.insert(USER_AGENT, HeaderValue::from_static(ua)); - headers.insert( - ACCEPT, - HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"), - ); - headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.9")); - #[cfg(all(feature = "gzip", feature = "deflate", feature = "brotli"))] - headers.insert( - ACCEPT_ENCODING, - HeaderValue::from_static("gzip, deflate, br"), - ); - headers -} +mod header { + use super::*; + + #[inline] + pub fn header_initializer_for_15(ua: &'static str) -> HeaderMap { + let mut headers = HeaderMap::new(); + headers.insert(USER_AGENT, HeaderValue::from_static(ua)); + headers.insert( + ACCEPT, + HeaderValue::from_static( + "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + ), + ); + headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.9")); + #[cfg(all(feature = "gzip", feature = "deflate", feature = "brotli"))] + headers.insert( + ACCEPT_ENCODING, + HeaderValue::from_static("gzip, deflate, br"), + ); + headers + } -#[inline] -fn header_initializer_for_16_17(ua: &'static str) -> HeaderMap { - let mut headers = HeaderMap::new(); - headers.insert( - ACCEPT, - HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"), - ); - headers.insert("sec-fetch-site", HeaderValue::from_static("none")); - #[cfg(all(feature = "gzip", feature = "deflate", feature = "brotli"))] - headers.insert( - ACCEPT_ENCODING, - HeaderValue::from_static("gzip, deflate, br"), - ); - headers.insert("sec-fetch-mode", HeaderValue::from_static("navigate")); - headers.insert(USER_AGENT, HeaderValue::from_static(ua)); - headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.9")); - headers.insert("sec-fetch-dest", HeaderValue::from_static("document")); - headers -} + #[inline] + pub fn header_initializer_for_16_17(ua: &'static str) -> HeaderMap { + let mut headers = HeaderMap::new(); + headers.insert( + ACCEPT, + HeaderValue::from_static( + "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + ), + ); + headers.insert("sec-fetch-site", HeaderValue::from_static("none")); + #[cfg(all(feature = "gzip", feature = "deflate", feature = "brotli"))] + headers.insert( + ACCEPT_ENCODING, + HeaderValue::from_static("gzip, deflate, br"), + ); + headers.insert("sec-fetch-mode", HeaderValue::from_static("navigate")); + headers.insert(USER_AGENT, HeaderValue::from_static(ua)); + headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.9")); + headers.insert("sec-fetch-dest", HeaderValue::from_static("document")); + headers + } -#[inline] -fn header_initializer_for_18(ua: &'static str) -> HeaderMap { - let mut headers = HeaderMap::new(); - headers.insert("sec-fetch-dest", HeaderValue::from_static("document")); - headers.insert(USER_AGENT, HeaderValue::from_static(ua)); - headers.insert( - ACCEPT, - HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"), - ); - headers.insert("sec-fetch-site", HeaderValue::from_static("none")); - headers.insert("sec-fetch-mode", HeaderValue::from_static("navigate")); - headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.9")); - headers.insert("priority", HeaderValue::from_static("u=0, i")); - #[cfg(all(feature = "gzip", feature = "deflate", feature = "brotli"))] - headers.insert( - ACCEPT_ENCODING, - HeaderValue::from_static("gzip, deflate, br"), - ); - headers + #[inline] + pub fn header_initializer_for_18(ua: &'static str) -> HeaderMap { + let mut headers = HeaderMap::new(); + headers.insert("sec-fetch-dest", HeaderValue::from_static("document")); + headers.insert(USER_AGENT, HeaderValue::from_static(ua)); + headers.insert( + ACCEPT, + HeaderValue::from_static( + "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + ), + ); + headers.insert("sec-fetch-site", HeaderValue::from_static("none")); + headers.insert("sec-fetch-mode", HeaderValue::from_static("navigate")); + headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.9")); + headers.insert("priority", HeaderValue::from_static("u=0, i")); + #[cfg(all(feature = "gzip", feature = "deflate", feature = "brotli"))] + headers.insert( + ACCEPT_ENCODING, + HeaderValue::from_static("gzip, deflate, br"), + ); + headers + } } mod tls { use super::tls_imports::*; - pub const CURVES: &[SslCurve] = &[ - SslCurve::X25519, - SslCurve::SECP256R1, - SslCurve::SECP384R1, - SslCurve::SECP521R1, - ]; + pub const CURVES: &str = join!(":", "X25519", "P-256", "P-384", "P-521"); pub const CIPHER_LIST_1: &str = join!( ":", @@ -182,7 +254,6 @@ mod tls { "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_RSA_WITH_3DES_EDE_CBC_SHA" ); - pub const CIPHER_LIST_2: &str = join!( ":", "TLS_AES_128_GCM_SHA256", @@ -236,13 +307,13 @@ mod tls { "rsa_pkcs1_sha1" ); - pub const CERT_COMPRESSION_ALGORITHM: &[CertCompressionAlgorithm] = - &[CertCompressionAlgorithm::Zlib]; + pub const CERT_COMPRESSION_ALGORITHM: &[CertificateCompressionAlgorithm] = + &[CertificateCompressionAlgorithm::ZLIB]; #[derive(TypedBuilder)] pub struct SafariTlsConfig { #[builder(default = CURVES)] - curves: &'static [SslCurve], + curves: &'static str, #[builder(default = SIGALGS_LIST)] sigalgs_list: &'static str, @@ -250,68 +321,30 @@ mod tls { cipher_list: &'static str, } - impl From for TlsConfig { + impl From for TlsOptions { fn from(val: SafariTlsConfig) -> Self { - TlsConfig::builder() + TlsOptions::builder() .session_ticket(false) .grease_enabled(true) .enable_ocsp_stapling(true) .enable_signed_cert_timestamps(true) - .curves(val.curves) + .curves_list(val.curves) .sigalgs_list(val.sigalgs_list) .cipher_list(val.cipher_list) .min_tls_version(TlsVersion::TLS_1_0) - .cert_compression_algorithm(CERT_COMPRESSION_ALGORITHM) + .certificate_compression_algorithms(CERT_COMPRESSION_ALGORITHM) .build() } } - - impl From for Option { - #[inline(always)] - fn from(val: SafariTlsConfig) -> Self { - Some(val.into()) - } - } -} - -mod http2 { - use super::http2_imports::*; - - pub const HEADER_PRIORITY: (u32, u8, bool) = (0, 255, true); - pub const NEW_HEADER_PRIORITY: (u32, u8, bool) = (0, 255, false); - - pub const HEADERS_PSEUDO_ORDER: [PseudoOrder; 4] = [Method, Scheme, Path, Authority]; - pub const NEW_HEADERS_PSEUDO_ORDER: [PseudoOrder; 4] = [Method, Scheme, Authority, Path]; - - pub const SETTINGS_ORDER: [SettingsOrder; 8] = [ - HeaderTableSize, - EnablePush, - InitialWindowSize, - MaxConcurrentStreams, - MaxFrameSize, - MaxHeaderListSize, - UnknownSetting8, - UnknownSetting9, - ]; - pub const NEW_SETTINGS_ORDER: [SettingsOrder; 8] = [ - HeaderTableSize, - EnablePush, - MaxConcurrentStreams, - InitialWindowSize, - MaxFrameSize, - MaxHeaderListSize, - UnknownSetting8, - UnknownSetting9, - ]; } macro_rules! mod_generator { - ($mod_name:ident, $tls_config:expr, $http2_config:expr, $header_initializer:ident, $ua:expr) => { + ($mod_name:ident, $tls_options:expr, $http2_options:expr, $header_initializer:ident, $ua:expr) => { pub(crate) mod $mod_name { use super::*; #[inline(always)] - pub fn emulation(option: EmulationOption) -> EmulationProvider { + pub fn emulation(option: EmulationOption) -> Emulation { let default_headers = if !option.skip_headers { Some($header_initializer($ua)) } else { @@ -325,12 +358,18 @@ macro_rules! mod_generator { pub fn build_emulation( option: EmulationOption, default_headers: Option, - ) -> EmulationProvider { - EmulationProvider::builder() - .tls_config($tls_config) - .http2_config(conditional_http2!(option.skip_http2, $http2_config)) - .default_headers(default_headers) - .build() + ) -> Emulation { + let mut builder = Emulation::builder().tls_options($tls_options); + + if !option.skip_http2 { + builder = builder.http2_options($http2_options); + } + + if let Some(headers) = default_headers { + builder = builder.headers(headers); + } + + builder.build() } } }; @@ -340,7 +379,7 @@ macro_rules! mod_generator { use super::*; #[inline(always)] - pub fn emulation(option: EmulationOption) -> EmulationProvider { + pub fn emulation(option: EmulationOption) -> Emulation { let default_headers = if !option.skip_headers { Some($header_initializer($ua)) } else { @@ -355,8 +394,8 @@ macro_rules! mod_generator { mod_generator!( safari15_3, - tls_config!(1, CIPHER_LIST_1), - http2_config!(4), + tls_options!(1, CIPHER_LIST_1), + http2_options!(4), header_initializer_for_15, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.3 Safari/605.1.15" ); @@ -370,8 +409,8 @@ mod_generator!( mod_generator!( safari15_6_1, - tls_config!(1, CIPHER_LIST_2), - http2_config!(4), + tls_options!(1, CIPHER_LIST_2), + http2_options!(4), header_initializer_for_15, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6.1 Safari/605.1.15" ); @@ -399,16 +438,16 @@ mod_generator!( mod_generator!( safari_ios_16_5, - tls_config!(1, CIPHER_LIST_2), - http2_config!(1), + tls_options!(1, CIPHER_LIST_2), + http2_options!(1), header_initializer_for_16_17, "Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Mobile/15E148 Safari/604.1" ); mod_generator!( safari17_0, - tls_config!(1, CIPHER_LIST_2), - http2_config!(5), + tls_options!(1, CIPHER_LIST_2), + http2_options!(5), header_initializer_for_16_17, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15" ); @@ -429,8 +468,8 @@ mod_generator!( mod_generator!( safari_ios_17_2, - tls_config!(1, CIPHER_LIST_2), - http2_config!(2), + tls_options!(1, CIPHER_LIST_2), + http2_options!(2), header_initializer_for_16_17, "Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1" ); @@ -444,8 +483,8 @@ mod_generator!( mod_generator!( safari18, - tls_config!(1, CIPHER_LIST_2), - http2_config!(3), + tls_options!(1, CIPHER_LIST_2), + http2_options!(3), header_initializer_for_18, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15" ); @@ -466,8 +505,8 @@ mod_generator!( mod_generator!( safari18_2, - tls_config!(2, CIPHER_LIST_2, NEW_SIGALGS_LIST), - http2_config!(3), + tls_options!(2, CIPHER_LIST_2, NEW_SIGALGS_LIST), + http2_options!(3), header_initializer_for_18, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.2 Safari/605.1.15" ); @@ -488,8 +527,8 @@ mod_generator!( mod_generator!( safari18_5, - tls_config!(2, CIPHER_LIST_2, NEW_SIGALGS_LIST), - http2_config!(6), + tls_options!(2, CIPHER_LIST_2, NEW_SIGALGS_LIST), + http2_options!(6), header_initializer_for_18, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Safari/605.1.15" ); diff --git a/src/emulation/mod.rs b/src/emulation/mod.rs index f1f5fc1..ad9a490 100644 --- a/src/emulation/mod.rs +++ b/src/emulation/mod.rs @@ -8,7 +8,6 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "emulation-rand")] use strum_macros::VariantArray; use typed_builder::TypedBuilder; -use wreq::{EmulationProvider, EmulationProviderFactory}; macro_rules! define_emulation_enum { ($(#[$meta:meta])* $name:ident, $default_variant:ident, $($variant:ident => $rename:expr),*) => { @@ -144,8 +143,8 @@ define_emulation_enum!( ); /// ======== Emulation impls ======== -impl EmulationProviderFactory for Emulation { - fn emulation(self) -> EmulationProvider { +impl wreq::EmulationFactory for Emulation { + fn emulation(self) -> wreq::Emulation { EmulationOption::builder() .emulation(self) .build() @@ -208,12 +207,12 @@ impl EmulationOS { /// Represents the configuration options for emulating a browser and operating system. /// -/// The `EmulationOption` struct allows you to configure various aspects of browser and OS emulation, -/// including the browser version, operating system, and whether to skip certain features like HTTP/2 -/// or headers. +/// The `EmulationOption` struct allows you to configure various aspects of browser and OS +/// emulation, including the browser version, operating system, and whether to skip certain features +/// like HTTP/2 or headers. /// -/// This struct is typically used to build an `EmulationProvider` that can be applied to HTTP clients -/// for making requests that mimic specific browser and OS configurations. +/// This struct is typically used to build an `EmulationProvider` that can be applied to HTTP +/// clients for making requests that mimic specific browser and OS configurations. /// /// # Fields /// @@ -267,8 +266,8 @@ macro_rules! emulation_match { } } -impl EmulationProviderFactory for EmulationOption { - fn emulation(self) -> EmulationProvider { +impl wreq::EmulationFactory for EmulationOption { + fn emulation(self) -> wreq::Emulation { emulation_match!( self.emulation, self, diff --git a/src/emulation/rand.rs b/src/emulation/rand.rs index 5254a4d..b1c9ec5 100644 --- a/src/emulation/rand.rs +++ b/src/emulation/rand.rs @@ -1,10 +1,14 @@ -use super::{Emulation, EmulationOS, EmulationOption}; -use std::cell::Cell; -use std::collections::hash_map::RandomState; -use std::hash::{BuildHasher, Hasher}; -use std::num::Wrapping; +use std::{ + cell::Cell, + collections::hash_map::RandomState, + hash::{BuildHasher, Hasher}, + num::Wrapping, +}; + use strum::VariantArray; +use super::{Emulation, EmulationOS, EmulationOption}; + // from: https://github.com/seanmonstar/reqwest/blob/44ac897f1ab35ba24a195927043d185d5cbb6912/src/util.rs#L27 fn fast_random() -> u64 { thread_local! { @@ -71,9 +75,12 @@ impl Emulation { #[cfg(test)] mod tests { + use std::{ + sync::{Arc, Mutex}, + thread, + }; + use super::*; - use std::sync::{Arc, Mutex}; - use std::thread; #[test] fn test_concurrent_get_random_emulation() { diff --git a/tests/emulation_chrome.rs b/tests/emulation_chrome.rs index c6395b2..f3cdc41 100644 --- a/tests/emulation_chrome.rs +++ b/tests/emulation_chrome.rs @@ -1,10 +1,11 @@ #[macro_use] mod support; -use support::CLIENT; use wreq_util::Emulation; -// Enabling certain extensions will change the length during encryption. This is because TLS will automatically use padding to fill the data and add a padding extension. At this time, the ja4 fingerprint will change. +// Enabling certain extensions will change the length during encryption. This is because TLS will +// automatically use padding to fill the data and add a padding extension. At this time, the ja4 +// fingerprint will change. test_emulation!( test_chrome100, diff --git a/tests/emulation_firefox.rs b/tests/emulation_firefox.rs index a36f562..91cbc2e 100644 --- a/tests/emulation_firefox.rs +++ b/tests/emulation_firefox.rs @@ -1,7 +1,6 @@ #[macro_use] mod support; -use support::CLIENT; use wreq_util::Emulation; test_emulation!( diff --git a/tests/emulation_okhttp.rs b/tests/emulation_okhttp.rs index 11a7ec6..e65b527 100644 --- a/tests/emulation_okhttp.rs +++ b/tests/emulation_okhttp.rs @@ -1,7 +1,6 @@ #[macro_use] mod support; -use support::CLIENT; use wreq_util::Emulation; test_emulation!( diff --git a/tests/emulation_opera.rs b/tests/emulation_opera.rs index d23ba27..5bcfc52 100644 --- a/tests/emulation_opera.rs +++ b/tests/emulation_opera.rs @@ -1,10 +1,11 @@ #[macro_use] mod support; -use support::CLIENT; use wreq_util::Emulation; -// Enabling certain extensions will change the length during encryption. This is because TLS will automatically use padding to fill the data and add a padding extension. At this time, the ja4 fingerprint will change. +// Enabling certain extensions will change the length during encryption. This is because TLS will +// automatically use padding to fill the data and add a padding extension. At this time, the ja4 +// fingerprint will change. test_emulation!( test_opera116, diff --git a/tests/emulation_safari.rs b/tests/emulation_safari.rs index a1f8f7b..9065996 100644 --- a/tests/emulation_safari.rs +++ b/tests/emulation_safari.rs @@ -1,7 +1,6 @@ #[macro_use] mod support; -use support::CLIENT; use wreq_util::Emulation; test_emulation!( diff --git a/tests/support/delay_server.rs b/tests/support/delay_server.rs index 747fdd7..9922b03 100644 --- a/tests/support/delay_server.rs +++ b/tests/support/delay_server.rs @@ -1,16 +1,11 @@ #![cfg(not(target_arch = "wasm32"))] #![allow(unused)] -use std::convert::Infallible; -use std::future::Future; -use std::net; -use std::time::Duration; +use std::{convert::Infallible, future::Future, net, time::Duration}; use futures_util::FutureExt; use http::{Request, Response}; use hyper::service::service_fn; -use tokio::net::TcpListener; -use tokio::select; -use tokio::sync::oneshot; +use tokio::{net::TcpListener, select, sync::oneshot}; /// This server, unlike [`super::server::Server`], allows for delaying the /// specified amount of time after each TCP connection is established. This is diff --git a/tests/support/mod.rs b/tests/support/mod.rs index 4c01d9c..2e1cd34 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -4,6 +4,8 @@ pub mod delay_server; pub mod server; use std::{sync::LazyLock, time::Duration}; + +use tokio::sync::Semaphore; use wreq::Client; // TODO: remove once done converting to new support server? @@ -11,9 +13,11 @@ use wreq::Client; pub static DEFAULT_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); +pub static TEST_SEMAPHORE: LazyLock = LazyLock::new(|| Semaphore::new(1)); + pub static CLIENT: LazyLock = LazyLock::new(|| { Client::builder() - .connect_timeout(Duration::from_secs(30)) + .connect_timeout(Duration::from_secs(60)) .build() .unwrap() }); @@ -23,11 +27,11 @@ macro_rules! test_emulation { ($test_name:ident, $emulation:expr, $ja4:expr, $akamai_hash:expr) => { #[tokio::test] async fn $test_name() { - let client = CLIENT.cloned(); - client.update().emulation($emulation).apply().unwrap(); + let _permit = crate::support::TEST_SEMAPHORE.acquire().await.unwrap(); - let resp = client + let resp = crate::support::CLIENT .get("https://tls.browserleaks.com/") + .emulation($emulation) .send() .await .unwrap(); @@ -47,7 +51,7 @@ macro_rules! test_emulation { } assert!(conditional); - tokio::time::sleep(std::time::Duration::from_millis(200)).await; + tokio::time::sleep(std::time::Duration::from_millis(1000)).await; } }; } diff --git a/tests/support/server.rs b/tests/support/server.rs index 46f87e6..8b190fd 100644 --- a/tests/support/server.rs +++ b/tests/support/server.rs @@ -1,15 +1,9 @@ #![cfg(not(target_arch = "wasm32"))] -use std::convert::Infallible; -use std::future::Future; -use std::net; -use std::sync::mpsc as std_mpsc; -use std::thread; -use std::time::Duration; - -use tokio::io::AsyncReadExt; -use tokio::net::TcpStream; -use tokio::runtime; -use tokio::sync::oneshot; +use std::{ + convert::Infallible, future::Future, net, sync::mpsc as std_mpsc, thread, time::Duration, +}; + +use tokio::{io::AsyncReadExt, net::TcpStream, runtime, sync::oneshot}; pub struct Server { addr: net::SocketAddr, From f62f66106718ae5314a448bb61c52e22a8c0e70e Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Mon, 4 Aug 2025 12:02:51 +0800 Subject: [PATCH 2/3] fix build --- Cargo.toml | 25 ++++--------------------- src/emulation/device/firefox.rs | 1 + 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a5501c7..a977d02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,8 +19,6 @@ targets = ["x86_64-unknown-linux-gnu"] [features] default = ["emulation", "gzip", "brotli", "deflate", "zstd"] -full = ["emulation", "emulation-rand", "gzip", "brotli", "deflate", "zstd"] - # Emulation devices emulation = ["dep:typed-builder"] @@ -37,7 +35,7 @@ deflate = ["wreq/deflate"] zstd = ["wreq/zstd"] [dependencies] -wreq = { version = "6.0.0-rc.1", default-features = false } +wreq = { version = "6.0.0-rc.2", default-features = false } serde = { version = "1.0", features = ["derive"], optional = true } typed-builder = { version = "0.21.0", optional = true } strum = { version = "0.27.2", optional = true } @@ -51,7 +49,7 @@ hyper = { version = "1.1.0", default-features = false, features = [ "client", "server", ] } -hyper-util = { version = "0.1.10", features = [ +hyper-util = { version = "0.1.16", features = [ "http1", "http2", "client", @@ -93,14 +91,7 @@ path = "tests/emulation_safari.rs" [[example]] name = "emulation" path = "examples/emulation.rs" -required-features = [ - "emulation", - "gzip", - "brotli", - "deflate", - "zstd", - "wreq/full", -] +required-features = ["emulation", "gzip", "brotli", "deflate", "zstd"] [[example]] name = "emulation_rand" @@ -111,18 +102,10 @@ required-features = [ "brotli", "deflate", "zstd", - "wreq/full", "emulation-rand", ] [[example]] name = "emulation_option" path = "examples/emulation_option.rs" -required-features = [ - "emulation", - "gzip", - "brotli", - "deflate", - "zstd", - "wreq/full", -] +required-features = ["emulation", "gzip", "brotli", "deflate", "zstd"] diff --git a/src/emulation/device/firefox.rs b/src/emulation/device/firefox.rs index 2cbcc74..e36e958 100644 --- a/src/emulation/device/firefox.rs +++ b/src/emulation/device/firefox.rs @@ -403,6 +403,7 @@ mod tls { .pre_shared_key(val.pre_shared_key) .psk_skip_session_ticket(val.psk_skip_session_tickets) .psk_dhe_ke(val.psk_dhe_ke) + .prefer_chacha20(true) .extension_permutation(val.extension_permutation) .aes_hw_override(true) .random_aes_hw_override(true); From c9d5296513b1d90e12c4d85c038969292e1d3db1 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Mon, 4 Aug 2025 16:23:51 +0800 Subject: [PATCH 3/3] fmt mod --- src/emulation/device/chrome/header.rs | 59 ++ src/emulation/device/chrome/macros.rs | 201 +++++ .../device/{chrome.rs => chrome/mod.rs} | 383 +-------- src/emulation/device/chrome/tls.rs | 87 +++ src/emulation/device/firefox.rs | 734 ------------------ src/emulation/device/firefox/header.rs | 21 + src/emulation/device/firefox/macros.rs | 260 +++++++ src/emulation/device/firefox/mod.rs | 237 ++++++ src/emulation/device/firefox/tls.rs | 188 +++++ src/emulation/device/okhttp/macros.rs | 11 + .../device/{okhttp.rs => okhttp/mod.rs} | 108 ++- src/emulation/device/opera.rs | 359 --------- src/emulation/device/opera/header.rs | 35 + src/emulation/device/opera/macros.rs | 156 ++++ src/emulation/device/opera/mod.rs | 82 ++ src/emulation/device/opera/tls.rs | 85 ++ src/emulation/device/safari.rs | 534 ------------- src/emulation/device/safari/header.rs | 59 ++ src/emulation/device/safari/macros.rs | 201 +++++ src/emulation/device/safari/mod.rs | 150 ++++ src/emulation/device/safari/tls.rs | 115 +++ 21 files changed, 1996 insertions(+), 2069 deletions(-) create mode 100644 src/emulation/device/chrome/header.rs create mode 100644 src/emulation/device/chrome/macros.rs rename src/emulation/device/{chrome.rs => chrome/mod.rs} (79%) create mode 100644 src/emulation/device/chrome/tls.rs delete mode 100644 src/emulation/device/firefox.rs create mode 100644 src/emulation/device/firefox/header.rs create mode 100644 src/emulation/device/firefox/macros.rs create mode 100644 src/emulation/device/firefox/mod.rs create mode 100644 src/emulation/device/firefox/tls.rs create mode 100644 src/emulation/device/okhttp/macros.rs rename src/emulation/device/{okhttp.rs => okhttp/mod.rs} (85%) delete mode 100644 src/emulation/device/opera.rs create mode 100644 src/emulation/device/opera/header.rs create mode 100644 src/emulation/device/opera/macros.rs create mode 100644 src/emulation/device/opera/mod.rs create mode 100644 src/emulation/device/opera/tls.rs delete mode 100644 src/emulation/device/safari.rs create mode 100644 src/emulation/device/safari/header.rs create mode 100644 src/emulation/device/safari/macros.rs create mode 100644 src/emulation/device/safari/mod.rs create mode 100644 src/emulation/device/safari/tls.rs diff --git a/src/emulation/device/chrome/header.rs b/src/emulation/device/chrome/header.rs new file mode 100644 index 0000000..d080ef2 --- /dev/null +++ b/src/emulation/device/chrome/header.rs @@ -0,0 +1,59 @@ +use super::*; + +pub fn header_initializer( + sec_ch_ua: &'static str, + ua: &'static str, + emulation_os: EmulationOS, +) -> HeaderMap { + let mut headers = HeaderMap::new(); + header_chrome_sec_ch_ua!( + headers, + sec_ch_ua, + emulation_os.platform(), + emulation_os.is_mobile() + ); + header_chrome_ua!(headers, ua); + header_chrome_sec_fetch!(headers); + header_chrome_accpet!(headers); + headers +} + +pub fn header_initializer_with_zstd( + sec_ch_ua: &'static str, + ua: &'static str, + emulation_os: EmulationOS, +) -> HeaderMap { + let mut headers = HeaderMap::new(); + header_chrome_sec_ch_ua!( + headers, + sec_ch_ua, + emulation_os.platform(), + emulation_os.is_mobile() + ); + header_chrome_ua!(headers, ua); + header_chrome_sec_fetch!(headers); + header_chrome_accpet!(zstd, headers); + headers +} + +pub fn header_initializer_with_zstd_priority( + sec_ch_ua: &'static str, + ua: &'static str, + emulation_os: EmulationOS, +) -> HeaderMap { + let mut headers = HeaderMap::new(); + header_chrome_sec_ch_ua!( + headers, + sec_ch_ua, + emulation_os.platform(), + emulation_os.is_mobile() + ); + header_chrome_ua!(headers, ua); + header_chrome_sec_fetch!(headers); + header_chrome_accpet!(zstd, headers); + headers.insert( + HeaderName::from_static("priority"), + HeaderValue::from_static("u=0, i"), + ); + headers +} diff --git a/src/emulation/device/chrome/macros.rs b/src/emulation/device/chrome/macros.rs new file mode 100644 index 0000000..699748a --- /dev/null +++ b/src/emulation/device/chrome/macros.rs @@ -0,0 +1,201 @@ +macro_rules! headers_stream_dependency { + () => { + StreamDependency::new(StreamId::zero(), 255, true) + }; +} + +macro_rules! pseudo_order { + () => { + PseudoOrder::builder() + .extend([ + PseudoId::Method, + PseudoId::Authority, + PseudoId::Scheme, + PseudoId::Path, + ]) + .build() + }; +} + +macro_rules! settings_order { + () => { + SettingsOrder::builder() + .extend([ + SettingId::HeaderTableSize, + SettingId::EnablePush, + SettingId::MaxConcurrentStreams, + SettingId::InitialWindowSize, + SettingId::MaxFrameSize, + SettingId::MaxHeaderListSize, + SettingId::EnableConnectProtocol, + SettingId::NoRfc7540Priorities, + ]) + .build() + }; +} + +macro_rules! tls_options { + (@build $builder:expr) => { + $builder.build().into() + }; + + (1) => { + tls_options!(@build ChromeTlsConfig::builder()) + }; + (2) => { + tls_options!(@build ChromeTlsConfig::builder().enable_ech_grease(true)) + }; + (3) => { + tls_options!(@build ChromeTlsConfig::builder().permute_extensions(true)) + }; + (4) => { + tls_options!(@build ChromeTlsConfig::builder() + .permute_extensions(true) + .enable_ech_grease(true)) + }; + (5) => { + tls_options!(@build ChromeTlsConfig::builder() + .permute_extensions(true) + .enable_ech_grease(true) + .pre_shared_key(true)) + }; + (6, $curves:expr) => { + tls_options!(@build ChromeTlsConfig::builder() + .permute_extensions(true) + .enable_ech_grease(true) + .pre_shared_key(true) + .curves($curves)) + }; + (7, $curves:expr) => { + tls_options!(@build ChromeTlsConfig::builder() + .permute_extensions(true) + .enable_ech_grease(true) + .pre_shared_key(true) + .curves($curves) + .alps_use_new_codepoint(true)) + }; +} + +macro_rules! http2_options { + (@base $builder:expr) => { + $builder + .initial_window_size(6291456) + .initial_connection_window_size(15728640) + .max_header_list_size(262144) + .header_table_size(65536) + .headers_stream_dependency(headers_stream_dependency!()) + .headers_pseudo_order(pseudo_order!()) + .settings_order(settings_order!()) + }; + + (1) => { + http2_options!(@base Http2Options::builder()) + .max_concurrent_streams(1000) + .build() + }; + (2) => { + http2_options!(@base Http2Options::builder()) + .max_concurrent_streams(1000) + .enable_push(false) + .build() + }; + (3) => { + http2_options!(@base Http2Options::builder()) + .enable_push(false) + .build() + }; +} + +macro_rules! mod_generator { + ( + $mod_name:ident, + $tls_options:expr, + $http2_options:expr, + $header_initializer:ident, + [($default_os:ident, $default_sec_ch_ua:tt, $default_ua:tt) $(, ($other_os:ident, $other_sec_ch_ua:tt, $other_ua:tt))*] + ) => { + pub(crate) mod $mod_name { + use super::*; + + #[inline(always)] + pub fn emulation(option: EmulationOption) -> Emulation { + let default_headers = if !option.skip_headers { + #[allow(unreachable_patterns)] + let default_headers = match option.emulation_os { + $( + EmulationOS::$other_os => $header_initializer( + $other_sec_ch_ua, + $other_ua, + option.emulation_os, + ), + )* + _ => $header_initializer( + $default_sec_ch_ua, + $default_ua, + EmulationOS::$default_os, + ), + }; + Some(default_headers) + } else { + None + }; + + build_emulation(option, default_headers) + } + + #[inline(always)] + pub fn build_emulation( + option: EmulationOption, + default_headers: Option + ) -> Emulation { + let mut builder = Emulation::builder().tls_options($tls_options); + + if !option.skip_http2 { + builder = builder.http2_options($http2_options); + } + + if let Some(headers) = default_headers { + builder = builder.headers(headers); + } + + builder.build() + } + } + }; + ( + $mod_name:ident, + $build_emulation:expr, + $header_initializer:ident, + [($default_os:ident, $default_sec_ch_ua:tt, $default_ua:tt) $(, ($other_os:ident, $other_sec_ch_ua:tt, $other_ua:tt))*] + ) => { + pub(crate) mod $mod_name { + use super::*; + + #[inline(always)] + pub fn emulation(option: EmulationOption) -> Emulation { + let default_headers = if !option.skip_headers { + #[allow(unreachable_patterns)] + let default_headers = match option.emulation_os { + $( + EmulationOS::$other_os => $header_initializer( + $other_sec_ch_ua, + $other_ua, + option.emulation_os, + ), + )* + _ => $header_initializer( + $default_sec_ch_ua, + $default_ua, + EmulationOS::$default_os, + ), + }; + Some(default_headers) + } else { + None + }; + + $build_emulation(option, default_headers) + } + } + }; +} diff --git a/src/emulation/device/chrome.rs b/src/emulation/device/chrome/mod.rs similarity index 79% rename from src/emulation/device/chrome.rs rename to src/emulation/device/chrome/mod.rs index 219fbb6..849637d 100644 --- a/src/emulation/device/chrome.rs +++ b/src/emulation/device/chrome/mod.rs @@ -1,386 +1,13 @@ +#[macro_use] +mod macros; +mod header; +mod tls; + use header::*; use tls::*; use super::{emulation_imports::*, http2_imports::*, *}; -macro_rules! headers_stream_dependency { - () => { - StreamDependency::new(StreamId::zero(), 255, true) - }; -} - -macro_rules! pseudo_order { - () => { - PseudoOrder::builder() - .extend([ - PseudoId::Method, - PseudoId::Authority, - PseudoId::Scheme, - PseudoId::Path, - ]) - .build() - }; -} - -macro_rules! settings_order { - () => { - SettingsOrder::builder() - .extend([ - SettingId::HeaderTableSize, - SettingId::EnablePush, - SettingId::MaxConcurrentStreams, - SettingId::InitialWindowSize, - SettingId::MaxFrameSize, - SettingId::MaxHeaderListSize, - SettingId::EnableConnectProtocol, - SettingId::NoRfc7540Priorities, - ]) - .build() - }; -} - -macro_rules! tls_options { - (1) => { - ChromeTlsConfig::builder().build().into() - }; - (2) => { - ChromeTlsConfig::builder() - .enable_ech_grease(true) - .build() - .into() - }; - (3) => { - ChromeTlsConfig::builder() - .permute_extensions(true) - .build() - .into() - }; - (4) => { - ChromeTlsConfig::builder() - .permute_extensions(true) - .enable_ech_grease(true) - .build() - .into() - }; - (5) => { - ChromeTlsConfig::builder() - .permute_extensions(true) - .enable_ech_grease(true) - .pre_shared_key(true) - .build() - .into() - }; - (6, $curves:expr) => { - ChromeTlsConfig::builder() - .curves($curves) - .permute_extensions(true) - .pre_shared_key(true) - .enable_ech_grease(true) - .build() - .into() - }; - (7, $curves:expr) => { - ChromeTlsConfig::builder() - .curves($curves) - .permute_extensions(true) - .pre_shared_key(true) - .enable_ech_grease(true) - .alps_use_new_codepoint(true) - .build() - .into() - }; -} - -macro_rules! http2_options { - (1) => { - Http2Options::builder() - .initial_window_size(6291456) - .initial_connection_window_size(15728640) - .max_concurrent_streams(1000) - .max_header_list_size(262144) - .header_table_size(65536) - .headers_stream_dependency(headers_stream_dependency!()) - .headers_pseudo_order(pseudo_order!()) - .settings_order(settings_order!()) - .build() - }; - (2) => { - Http2Options::builder() - .initial_window_size(6291456) - .initial_connection_window_size(15728640) - .max_concurrent_streams(1000) - .max_header_list_size(262144) - .header_table_size(65536) - .enable_push(false) - .headers_stream_dependency(headers_stream_dependency!()) - .headers_pseudo_order(pseudo_order!()) - .settings_order(settings_order!()) - .build() - }; - (3) => { - Http2Options::builder() - .initial_window_size(6291456) - .initial_connection_window_size(15728640) - .max_header_list_size(262144) - .header_table_size(65536) - .enable_push(false) - .headers_stream_dependency(headers_stream_dependency!()) - .headers_pseudo_order(pseudo_order!()) - .settings_order(settings_order!()) - .build() - }; -} - -mod header { - - use super::*; - - #[inline] - pub fn header_initializer( - sec_ch_ua: &'static str, - ua: &'static str, - emulation_os: EmulationOS, - ) -> HeaderMap { - let mut headers = HeaderMap::new(); - header_chrome_sec_ch_ua!( - headers, - sec_ch_ua, - emulation_os.platform(), - emulation_os.is_mobile() - ); - header_chrome_ua!(headers, ua); - header_chrome_sec_fetch!(headers); - header_chrome_accpet!(headers); - headers - } - - #[inline] - pub fn header_initializer_with_zstd( - sec_ch_ua: &'static str, - ua: &'static str, - emulation_os: EmulationOS, - ) -> HeaderMap { - let mut headers = HeaderMap::new(); - header_chrome_sec_ch_ua!( - headers, - sec_ch_ua, - emulation_os.platform(), - emulation_os.is_mobile() - ); - header_chrome_ua!(headers, ua); - header_chrome_sec_fetch!(headers); - header_chrome_accpet!(zstd, headers); - headers - } - - #[inline] - pub fn header_initializer_with_zstd_priority( - sec_ch_ua: &'static str, - ua: &'static str, - emulation_os: EmulationOS, - ) -> HeaderMap { - let mut headers = HeaderMap::new(); - header_chrome_sec_ch_ua!( - headers, - sec_ch_ua, - emulation_os.platform(), - emulation_os.is_mobile() - ); - header_chrome_ua!(headers, ua); - header_chrome_sec_fetch!(headers); - header_chrome_accpet!(zstd, headers); - headers.insert( - HeaderName::from_static("priority"), - HeaderValue::from_static("u=0, i"), - ); - headers - } -} - -mod tls { - use super::tls_imports::*; - - pub const CURVES_1: &str = join!(":", "X25519", "P-256", "P-384"); - pub const CURVES_2: &str = join!(":", "X25519Kyber768Draft00", "X25519", "P-256", "P-384"); - pub const CURVES_3: &str = join!(":", "X25519MLKEM768", "X25519", "P-256", "P-384"); - - pub const CIPHER_LIST: &str = join!( - ":", - "TLS_AES_128_GCM_SHA256", - "TLS_AES_256_GCM_SHA384", - "TLS_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "TLS_RSA_WITH_AES_128_GCM_SHA256", - "TLS_RSA_WITH_AES_256_GCM_SHA384", - "TLS_RSA_WITH_AES_128_CBC_SHA", - "TLS_RSA_WITH_AES_256_CBC_SHA" - ); - - pub const SIGALGS_LIST: &str = join!( - ":", - "ecdsa_secp256r1_sha256", - "rsa_pss_rsae_sha256", - "rsa_pkcs1_sha256", - "ecdsa_secp384r1_sha384", - "rsa_pss_rsae_sha384", - "rsa_pkcs1_sha384", - "rsa_pss_rsae_sha512", - "rsa_pkcs1_sha512" - ); - - pub const CERT_COMPRESSION_ALGORITHM: &[CertificateCompressionAlgorithm] = - &[CertificateCompressionAlgorithm::BROTLI]; - - #[derive(TypedBuilder)] - pub struct ChromeTlsConfig { - #[builder(default = CURVES_1)] - curves: &'static str, - - #[builder(default = SIGALGS_LIST)] - sigalgs_list: &'static str, - - #[builder(default = CIPHER_LIST)] - cipher_list: &'static str, - - #[builder(default = AlpsProtocol::HTTP2, setter(into))] - alps_protos: AlpsProtocol, - - #[builder(default = false)] - alps_use_new_codepoint: bool, - - #[builder(default = false, setter(into))] - enable_ech_grease: bool, - - #[builder(default = false, setter(into))] - permute_extensions: bool, - - #[builder(default = false, setter(into))] - pre_shared_key: bool, - } - - impl From for TlsOptions { - fn from(val: ChromeTlsConfig) -> Self { - TlsOptions::builder() - .grease_enabled(true) - .enable_ocsp_stapling(true) - .enable_signed_cert_timestamps(true) - .curves_list(val.curves) - .sigalgs_list(val.sigalgs_list) - .cipher_list(val.cipher_list) - .min_tls_version(TlsVersion::TLS_1_2) - .max_tls_version(TlsVersion::TLS_1_3) - .permute_extensions(val.permute_extensions) - .pre_shared_key(val.pre_shared_key) - .enable_ech_grease(val.enable_ech_grease) - .alps_protocols([val.alps_protos]) - .alps_use_new_codepoint(val.alps_use_new_codepoint) - .certificate_compression_algorithms(CERT_COMPRESSION_ALGORITHM) - .build() - } - } -} - -macro_rules! mod_generator { - ( - $mod_name:ident, - $tls_options:expr, - $http2_options:expr, - $header_initializer:ident, - [($default_os:ident, $default_sec_ch_ua:tt, $default_ua:tt) $(, ($other_os:ident, $other_sec_ch_ua:tt, $other_ua:tt))*] - ) => { - pub(crate) mod $mod_name { - use super::*; - - #[inline(always)] - pub fn emulation(option: EmulationOption) -> Emulation { - let default_headers = if !option.skip_headers { - #[allow(unreachable_patterns)] - let default_headers = match option.emulation_os { - $( - EmulationOS::$other_os => $header_initializer( - $other_sec_ch_ua, - $other_ua, - option.emulation_os, - ), - )* - _ => $header_initializer( - $default_sec_ch_ua, - $default_ua, - EmulationOS::$default_os, - ), - }; - Some(default_headers) - } else { - None - }; - - build_emulation(option, default_headers) - } - - #[inline(always)] - pub fn build_emulation( - option: EmulationOption, - default_headers: Option - ) -> Emulation { - let mut builder = Emulation::builder().tls_options($tls_options); - - if !option.skip_http2 { - builder = builder.http2_options($http2_options); - } - - if let Some(headers) = default_headers { - builder = builder.headers(headers); - } - - builder.build() - } - } - }; - ( - $mod_name:ident, - $build_emulation:expr, - $header_initializer:ident, - [($default_os:ident, $default_sec_ch_ua:tt, $default_ua:tt) $(, ($other_os:ident, $other_sec_ch_ua:tt, $other_ua:tt))*] - ) => { - pub(crate) mod $mod_name { - use super::*; - - #[inline(always)] - pub fn emulation(option: EmulationOption) -> Emulation { - let default_headers = if !option.skip_headers { - #[allow(unreachable_patterns)] - let default_headers = match option.emulation_os { - $( - EmulationOS::$other_os => $header_initializer( - $other_sec_ch_ua, - $other_ua, - option.emulation_os, - ), - )* - _ => $header_initializer( - $default_sec_ch_ua, - $default_ua, - EmulationOS::$default_os, - ), - }; - Some(default_headers) - } else { - None - }; - - $build_emulation(option, default_headers) - } - } - }; -} - mod_generator!( v100, tls_options!(1), diff --git a/src/emulation/device/chrome/tls.rs b/src/emulation/device/chrome/tls.rs new file mode 100644 index 0000000..59c6022 --- /dev/null +++ b/src/emulation/device/chrome/tls.rs @@ -0,0 +1,87 @@ +use super::tls_imports::*; + +pub const CURVES_1: &str = join!(":", "X25519", "P-256", "P-384"); +pub const CURVES_2: &str = join!(":", "X25519Kyber768Draft00", "X25519", "P-256", "P-384"); +pub const CURVES_3: &str = join!(":", "X25519MLKEM768", "X25519", "P-256", "P-384"); + +pub const CIPHER_LIST: &str = join!( + ":", + "TLS_AES_128_GCM_SHA256", + "TLS_AES_256_GCM_SHA384", + "TLS_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA" +); + +pub const SIGALGS_LIST: &str = join!( + ":", + "ecdsa_secp256r1_sha256", + "rsa_pss_rsae_sha256", + "rsa_pkcs1_sha256", + "ecdsa_secp384r1_sha384", + "rsa_pss_rsae_sha384", + "rsa_pkcs1_sha384", + "rsa_pss_rsae_sha512", + "rsa_pkcs1_sha512" +); + +pub const CERT_COMPRESSION_ALGORITHM: &[CertificateCompressionAlgorithm] = + &[CertificateCompressionAlgorithm::BROTLI]; + +#[derive(TypedBuilder)] +pub struct ChromeTlsConfig { + #[builder(default = CURVES_1)] + curves: &'static str, + + #[builder(default = SIGALGS_LIST)] + sigalgs_list: &'static str, + + #[builder(default = CIPHER_LIST)] + cipher_list: &'static str, + + #[builder(default = AlpsProtocol::HTTP2, setter(into))] + alps_protos: AlpsProtocol, + + #[builder(default = false)] + alps_use_new_codepoint: bool, + + #[builder(default = false, setter(into))] + enable_ech_grease: bool, + + #[builder(default = false, setter(into))] + permute_extensions: bool, + + #[builder(default = false, setter(into))] + pre_shared_key: bool, +} + +impl From for TlsOptions { + fn from(val: ChromeTlsConfig) -> Self { + TlsOptions::builder() + .grease_enabled(true) + .enable_ocsp_stapling(true) + .enable_signed_cert_timestamps(true) + .curves_list(val.curves) + .sigalgs_list(val.sigalgs_list) + .cipher_list(val.cipher_list) + .min_tls_version(TlsVersion::TLS_1_2) + .max_tls_version(TlsVersion::TLS_1_3) + .permute_extensions(val.permute_extensions) + .pre_shared_key(val.pre_shared_key) + .enable_ech_grease(val.enable_ech_grease) + .alps_protocols([val.alps_protos]) + .alps_use_new_codepoint(val.alps_use_new_codepoint) + .certificate_compression_algorithms(CERT_COMPRESSION_ALGORITHM) + .build() + } +} diff --git a/src/emulation/device/firefox.rs b/src/emulation/device/firefox.rs deleted file mode 100644 index e36e958..0000000 --- a/src/emulation/device/firefox.rs +++ /dev/null @@ -1,734 +0,0 @@ -use header::*; -use tls::*; - -use super::{emulation_imports::*, http2_imports::*, *}; - -macro_rules! headers_stream_dependency { - (1) => { - StreamDependency::new(StreamId::zero(), 41, false) - }; - (2) => { - StreamDependency::new(StreamId::from(13), 41, false) - }; -} - -macro_rules! pseudo_order { - () => { - PseudoOrder::builder() - .extend([ - PseudoId::Method, - PseudoId::Path, - PseudoId::Authority, - PseudoId::Scheme, - ]) - .build() - }; -} - -macro_rules! settings_order { - () => { - SettingsOrder::builder() - .extend([ - SettingId::HeaderTableSize, - SettingId::EnablePush, - SettingId::MaxConcurrentStreams, - SettingId::InitialWindowSize, - SettingId::MaxFrameSize, - SettingId::MaxHeaderListSize, - SettingId::EnableConnectProtocol, - SettingId::NoRfc7540Priorities, - ]) - .build() - }; -} - -macro_rules! http2_options { - (1) => { - Http2Options::builder() - .initial_stream_id(3) - .header_table_size(65536) - .enable_push(false) - .initial_window_size(131072) - .max_frame_size(16384) - .initial_connection_window_size(12517377 + 65535) - .headers_stream_dependency(headers_stream_dependency!(1)) - .headers_pseudo_order(pseudo_order!()) - .settings_order(settings_order!()) - .build() - }; - (2) => { - Http2Options::builder() - .initial_stream_id(15) - .header_table_size(65536) - .initial_window_size(131072) - .max_frame_size(16384) - .initial_connection_window_size(12517377 + 65535) - .headers_stream_dependency(headers_stream_dependency!(2)) - .headers_pseudo_order(pseudo_order!()) - .settings_order(settings_order!()) - .priorities( - Priorities::builder() - .extend([ - Priority::new( - StreamId::from(3), - StreamDependency::new(StreamId::zero(), 200, false), - ), - Priority::new( - StreamId::from(5), - StreamDependency::new(StreamId::zero(), 100, false), - ), - Priority::new( - StreamId::from(7), - StreamDependency::new(StreamId::zero(), 0, false), - ), - Priority::new( - StreamId::from(9), - StreamDependency::new(StreamId::from(7), 0, false), - ), - Priority::new( - StreamId::from(11), - StreamDependency::new(StreamId::from(3), 0, false), - ), - Priority::new( - StreamId::from(13), - StreamDependency::new(StreamId::zero(), 240, false), - ), - ]) - .build(), - ) - .build() - }; - (3) => { - Http2Options::builder() - .initial_stream_id(3) - .header_table_size(65536) - .enable_push(false) - .max_concurrent_streams(0) - .initial_window_size(131072) - .max_frame_size(16384) - .initial_connection_window_size(12517377 + 65535) - .headers_stream_dependency(headers_stream_dependency!(1)) - .headers_pseudo_order(pseudo_order!()) - .settings_order(settings_order!()) - .build() - }; - (4) => { - Http2Options::builder() - .initial_stream_id(3) - .header_table_size(4096) - .enable_push(false) - .initial_window_size(32768) - .max_frame_size(16384) - .initial_connection_window_size(12517377 + 65535) - .headers_stream_dependency(headers_stream_dependency!(1)) - .headers_pseudo_order(pseudo_order!()) - .settings_order(settings_order!()) - .build() - }; -} - -macro_rules! tls_options { - (1, $cipher_list:expr, $curves:expr) => { - FirefoxTlsConfig::builder() - .cipher_list($cipher_list) - .curves_list($curves) - .enable_ech_grease(true) - .pre_shared_key(true) - .psk_skip_session_tickets(true) - .key_shares_limit(3) - .certificate_compression_algorithms(CERT_COMPRESSION_ALGORITHM) - .build() - .into() - }; - (2, $cipher_list:expr, $curves:expr) => { - FirefoxTlsConfig::builder() - .cipher_list($cipher_list) - .curves_list($curves) - .key_shares_limit(2) - .build() - .into() - }; - (3, $cipher_list:expr, $curves:expr) => { - FirefoxTlsConfig::builder() - .cipher_list($cipher_list) - .curves_list($curves) - .session_ticket(false) - .enable_ech_grease(true) - .psk_dhe_ke(false) - .key_shares_limit(2) - .build() - .into() - }; - (4, $cipher_list:expr, $curves:expr) => { - FirefoxTlsConfig::builder() - .cipher_list($cipher_list) - .curves_list($curves) - .enable_ech_grease(true) - .enable_signed_cert_timestamps(true) - .session_ticket(true) - .pre_shared_key(true) - .psk_skip_session_tickets(true) - .key_shares_limit(3) - .certificate_compression_algorithms(CERT_COMPRESSION_ALGORITHM) - .build() - .into() - }; - (5, $cipher_list:expr, $curves:expr) => { - FirefoxTlsConfig::builder() - .cipher_list($cipher_list) - .curves_list($curves) - .enable_ech_grease(true) - .pre_shared_key(true) - .psk_skip_session_tickets(true) - .key_shares_limit(2) - .certificate_compression_algorithms(CERT_COMPRESSION_ALGORITHM) - .build() - .into() - }; - (6, $cipher_list:expr, $curves:expr) => { - FirefoxTlsConfig::builder() - .cipher_list($cipher_list) - .curves_list($curves) - .enable_ech_grease(true) - .enable_signed_cert_timestamps(true) - .session_ticket(false) - .psk_dhe_ke(false) - .key_shares_limit(3) - .certificate_compression_algorithms(CERT_COMPRESSION_ALGORITHM) - .build() - .into() - }; -} - -mod header { - use super::*; - - #[inline] - pub fn header_initializer(ua: &'static str) -> HeaderMap { - let mut headers = HeaderMap::new(); - header_firefox_ua!(headers, ua); - header_firefox_accept!(headers); - header_firefox_sec_fetch!(headers); - headers - } - - #[inline] - pub fn header_initializer_with_zstd(ua: &'static str) -> HeaderMap { - let mut headers = HeaderMap::new(); - header_firefox_ua!(headers, ua); - header_firefox_accept!(zstd, headers); - header_firefox_sec_fetch!(headers); - headers.insert( - HeaderName::from_static("priority"), - HeaderValue::from_static("u=0, i"), - ); - headers - } -} - -mod tls { - use super::tls_imports::*; - - pub const CURVES_1: &str = join!( - ":", - "X25519", - "P-256", - "P-384", - "P-521", - "ffdhe2048", - "ffdhe3072" - ); - pub const CURVES_2: &str = join!( - ":", - "X25519MLKEM768", - "X25519", - "P-256", - "P-384", - "P-521", - "ffdhe2048", - "ffdhe3072" - ); - - pub const CIPHER_LIST_1: &str = join!( - ":", - "TLS_AES_128_GCM_SHA256", - "TLS_CHACHA20_POLY1305_SHA256", - "TLS_AES_256_GCM_SHA384", - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "TLS_RSA_WITH_AES_128_GCM_SHA256", - "TLS_RSA_WITH_AES_256_GCM_SHA384", - "TLS_RSA_WITH_AES_128_CBC_SHA", - "TLS_RSA_WITH_AES_256_CBC_SHA" - ); - pub const CIPHER_LIST_2: &str = join!( - ":", - "TLS_AES_128_GCM_SHA256", - "TLS_CHACHA20_POLY1305_SHA256", - "TLS_AES_256_GCM_SHA384", - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "TLS_RSA_WITH_AES_128_GCM_SHA256", - "TLS_RSA_WITH_AES_256_GCM_SHA384", - "TLS_RSA_WITH_AES_128_CBC_SHA", - "TLS_RSA_WITH_AES_256_CBC_SHA" - ); - - pub const SIGALGS_LIST: &str = join!( - ":", - "ecdsa_secp256r1_sha256", - "ecdsa_secp384r1_sha384", - "ecdsa_secp521r1_sha512", - "rsa_pss_rsae_sha256", - "rsa_pss_rsae_sha384", - "rsa_pss_rsae_sha512", - "rsa_pkcs1_sha256", - "rsa_pkcs1_sha384", - "rsa_pkcs1_sha512", - "ecdsa_sha1", - "rsa_pkcs1_sha1" - ); - - pub const CERT_COMPRESSION_ALGORITHM: &[CertificateCompressionAlgorithm] = &[ - CertificateCompressionAlgorithm::ZLIB, - CertificateCompressionAlgorithm::BROTLI, - CertificateCompressionAlgorithm::ZSTD, - ]; - - pub const DELEGATED_CREDENTIALS: &str = join!( - ":", - "ecdsa_secp256r1_sha256", - "ecdsa_secp384r1_sha384", - "ecdsa_secp521r1_sha512", - "ecdsa_sha1" - ); - - pub const RECORD_SIZE_LIMIT: u16 = 0x4001; - - pub const EXTENSION_PERMUTATION_INDICES: &[ExtensionType] = &[ - ExtensionType::SERVER_NAME, - ExtensionType::EXTENDED_MASTER_SECRET, - ExtensionType::RENEGOTIATE, - ExtensionType::SUPPORTED_GROUPS, - ExtensionType::EC_POINT_FORMATS, - ExtensionType::SESSION_TICKET, - ExtensionType::APPLICATION_LAYER_PROTOCOL_NEGOTIATION, - ExtensionType::STATUS_REQUEST, - ExtensionType::DELEGATED_CREDENTIAL, - ExtensionType::CERTIFICATE_TIMESTAMP, - ExtensionType::KEY_SHARE, - ExtensionType::SUPPORTED_VERSIONS, - ExtensionType::SIGNATURE_ALGORITHMS, - ExtensionType::PSK_KEY_EXCHANGE_MODES, - ExtensionType::RECORD_SIZE_LIMIT, - ExtensionType::CERT_COMPRESSION, - ExtensionType::ENCRYPTED_CLIENT_HELLO, - ]; - - #[derive(TypedBuilder)] - pub struct FirefoxTlsConfig { - #[builder(default = SIGALGS_LIST)] - sigalgs_list: &'static str, - - #[builder(setter(into))] - cipher_list: &'static str, - - #[builder(setter(into))] - curves_list: &'static str, - - #[builder(default = true)] - session_ticket: bool, - - #[builder(default = false, setter(into))] - enable_ech_grease: bool, - - #[builder(default = false, setter(into))] - enable_signed_cert_timestamps: bool, - - #[builder(default = false, setter(into))] - pre_shared_key: bool, - - #[builder(default = false, setter(into))] - psk_skip_session_tickets: bool, - - #[builder(default = DELEGATED_CREDENTIALS, setter(into))] - delegated_credentials: &'static str, - - #[builder(default = RECORD_SIZE_LIMIT, setter(into))] - record_size_limit: u16, - - #[builder(default, setter(into))] - key_shares_limit: Option, - - #[builder(default = true, setter(into))] - psk_dhe_ke: bool, - - #[builder(default, setter(into))] - certificate_compression_algorithms: Option<&'static [CertificateCompressionAlgorithm]>, - - #[builder(default = EXTENSION_PERMUTATION_INDICES, setter(into))] - extension_permutation: &'static [ExtensionType], - } - - impl From for TlsOptions { - fn from(val: FirefoxTlsConfig) -> Self { - let mut builder = TlsOptions::builder() - .curves_list(val.curves_list) - .sigalgs_list(val.sigalgs_list) - .cipher_list(val.cipher_list) - .session_ticket(val.session_ticket) - .delegated_credentials(val.delegated_credentials) - .record_size_limit(val.record_size_limit) - .enable_ocsp_stapling(true) - .enable_ech_grease(val.enable_ech_grease) - .enable_signed_cert_timestamps(val.enable_signed_cert_timestamps) - .alpn_protocols([AlpnProtocol::HTTP2, AlpnProtocol::HTTP1]) - .min_tls_version(TlsVersion::TLS_1_2) - .max_tls_version(TlsVersion::TLS_1_3) - .key_shares_limit(val.key_shares_limit) - .pre_shared_key(val.pre_shared_key) - .psk_skip_session_ticket(val.psk_skip_session_tickets) - .psk_dhe_ke(val.psk_dhe_ke) - .prefer_chacha20(true) - .extension_permutation(val.extension_permutation) - .aes_hw_override(true) - .random_aes_hw_override(true); - - if let Some(cert_compression_algorithms) = val.certificate_compression_algorithms { - builder = builder.certificate_compression_algorithms(cert_compression_algorithms) - } - - builder.build() - } - } -} - -macro_rules! mod_generator { - ( - $mod_name:ident, - $tls_options:expr, - $http2_options:expr, - $header_initializer:ident, - [($default_os:ident, $default_ua:tt) $(, ($other_os:ident, $other_ua:tt))*] - ) => { - pub(crate) mod $mod_name { - use super::*; - - #[inline(always)] - pub fn emulation(option: EmulationOption) -> Emulation { - let default_headers = if !option.skip_headers { - #[allow(unreachable_patterns)] - let default_headers = match option.emulation_os { - $( - EmulationOS::$other_os => { - $header_initializer($other_ua) - } - ),* - _ => { - $header_initializer($default_ua) - } - }; - - Some(default_headers) - } else { - None - }; - - build_emulation(option, default_headers) - } - - #[inline(always)] - pub fn build_emulation( - option: EmulationOption, - default_headers: Option - ) -> Emulation { - let mut builder = Emulation::builder().tls_options($tls_options); - - if !option.skip_http2 { - builder = builder.http2_options($http2_options); - } - - if let Some(headers) = default_headers { - builder = builder.headers(headers); - } - - builder.build() - } - } - }; - ( - $mod_name:ident, - $build_emulation:expr, - $header_initializer:ident, - [($default_os:ident, $default_ua:tt) $(, ($other_os:ident, $other_ua:tt))*] - ) => { - pub(crate) mod $mod_name { - use super::*; - - #[inline(always)] - pub fn emulation(option: EmulationOption) -> Emulation { - let default_headers = if !option.skip_headers { - #[allow(unreachable_patterns)] - let default_headers = match option.emulation_os { - $( - EmulationOS::$other_os => { - $header_initializer($other_ua) - } - ),* - _ => { - $header_initializer($default_ua) - } - }; - - Some(default_headers) - } else { - None - }; - - $build_emulation(option, default_headers) - } - } - }; -} - -mod_generator!( - ff109, - tls_options!(2, CIPHER_LIST_1, CURVES_1), - http2_options!(2), - header_initializer, - [ - ( - Windows, - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0" - ), - ( - MacOS, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_17; rv:109.0) Gecko/20000101 Firefox/109.0" - ), - ( - Android, - "Mozilla/5.0 (Android 13; Mobile; rv:109.0) Gecko/109.0 Firefox/109.0" - ), - ( - Linux, - "Mozilla/5.0 (X11; Linux i686; rv:109.0) Gecko/20100101 Firefox/109.0" - ), - ( - IOS, - "Mozilla/5.0 (iPad; CPU OS 13_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/109.0 Mobile/15E148 Safari/605.1.15" - ) - ] -); - -mod_generator!( - ff117, - ff109::build_emulation, - header_initializer, - [ - ( - Windows, - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:117.0) Gecko/20100101 Firefox/117.0" - ), - ( - MacOS, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_16_1; rv:117.0) Gecko/20010101 Firefox/117.0" - ), - ( - Android, - "Mozilla/5.0 (Android 13; Mobile; rv:117.0) Gecko/117.0 Firefox/117.0" - ), - ( - Linux, - "Mozilla/5.0 (X11; Linux i686; rv:117.0) Gecko/20100101 Firefox/117.0" - ), - ( - IOS, - "Mozilla/5.0 (iPhone; CPU iPhone OS 14_7_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/117.0 Mobile/15E148 Safari/605.1.15" - ) - ] -); - -mod_generator!( - ff128, - tls_options!(3, CIPHER_LIST_2, CURVES_1), - http2_options!(3), - header_initializer_with_zstd, - [ - ( - MacOs, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:128.0) Gecko/20100101 Firefox/128.0" - ), - ( - Windows, - "Mozilla/5.0 (Windows NT 10.0; rv:128.0) Gecko/20100101 Firefox/128.0" - ), - ( - Android, - "Mozilla/5.0 (Android 13; Mobile; rv:128.0) Gecko/128.0 Firefox/128.0" - ), - ( - IOS, - "Mozilla/5.0 (iPhone; CPU iPhone OS 17_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/128.0 Mobile/15E148 Safari/605.1.15" - ), - ( - Linux, - "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0" - ) - ] -); - -mod_generator!( - ff133, - tls_options!(1, CIPHER_LIST_1, CURVES_2), - http2_options!(1), - header_initializer_with_zstd, - [ - ( - MacOs, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0" - ), - ( - Android, - "Mozilla/5.0 (Android 13; Mobile; rv:133.0) Gecko/133.0 Firefox/133.0" - ), - ( - Windows, - "Mozilla/5.0 (Windows NT 10.0; rv:133.0) Gecko/20100101 Firefox/133.0" - ), - ( - Linux, - "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0" - ), - ( - IOS, - "Mozilla/5.0 (iPhone; CPU iPhone OS 18_2_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/133.4 Mobile/15E148 Safari/605.1.15" - ) - ] -); - -mod_generator!( - ff135, - tls_options!(4, CIPHER_LIST_1, CURVES_2), - http2_options!(1), - header_initializer_with_zstd, - [ - ( - MacOS, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:135.0) Gecko/20100101 Firefox/135.0" - ), - ( - Windows, - "Mozilla/5.0 (Windows NT 10.0; rv:135.0) Gecko/20100101 Firefox/135.0" - ), - ( - Linux, - "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:135.0) Gecko/20100101 Firefox/135.0" - ) - ] -); - -mod_generator!( - ff_private_135, - tls_options!(6, CIPHER_LIST_1, CURVES_2), - http2_options!(1), - header_initializer_with_zstd, - [ - ( - MacOS, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:135.0) Gecko/20100101 Firefox/135.0" - ), - ( - Windows, - "Mozilla/5.0 (Windows NT 10.0; rv:135.0) Gecko/20100101 Firefox/135.0" - ), - ( - Linux, - "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:135.0) Gecko/20100101 Firefox/135.0" - ) - ] -); - -mod_generator!( - ff_android_135, - tls_options!(5, CIPHER_LIST_1, CURVES_1), - http2_options!(4), - header_initializer_with_zstd, - [( - Android, - "Mozilla/5.0 (Android 13; Mobile; rv:135.0) Gecko/135.0 Firefox/135.0" - )] -); - -mod_generator!( - ff136, - ff135::build_emulation, - header_initializer_with_zstd, - [ - ( - MacOS, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:136.0) Gecko/20100101 Firefox/136.0" - ), - ( - Windows, - "Mozilla/5.0 (Windows NT 10.0; rv:136.0) Gecko/20100101 Firefox/136.0" - ), - ( - Linux, - "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0" - ) - ] -); - -mod_generator!( - ff_private_136, - ff_private_135::build_emulation, - header_initializer_with_zstd, - [ - ( - MacOS, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:136.0) Gecko/20100101 Firefox/136.0" - ), - ( - Windows, - "Mozilla/5.0 (Windows NT 10.0; rv:136.0) Gecko/20100101 Firefox/136.0" - ), - ( - Linux, - "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0" - ) - ] -); - -mod_generator!( - ff139, - ff135::build_emulation, - header_initializer_with_zstd, - [ - ( - MacOS, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0" - ), - ( - Windows, - "Mozilla/5.0 (Windows NT 10.0; rv:136.0) Gecko/20100101 Firefox/139.0" - ), - ( - Linux, - "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/139.0" - ) - ] -); diff --git a/src/emulation/device/firefox/header.rs b/src/emulation/device/firefox/header.rs new file mode 100644 index 0000000..443bf69 --- /dev/null +++ b/src/emulation/device/firefox/header.rs @@ -0,0 +1,21 @@ +use super::*; + +pub fn header_initializer(ua: &'static str) -> HeaderMap { + let mut headers = HeaderMap::new(); + header_firefox_ua!(headers, ua); + header_firefox_accept!(headers); + header_firefox_sec_fetch!(headers); + headers +} + +pub fn header_initializer_with_zstd(ua: &'static str) -> HeaderMap { + let mut headers = HeaderMap::new(); + header_firefox_ua!(headers, ua); + header_firefox_accept!(zstd, headers); + header_firefox_sec_fetch!(headers); + headers.insert( + HeaderName::from_static("priority"), + HeaderValue::from_static("u=0, i"), + ); + headers +} diff --git a/src/emulation/device/firefox/macros.rs b/src/emulation/device/firefox/macros.rs new file mode 100644 index 0000000..1d51321 --- /dev/null +++ b/src/emulation/device/firefox/macros.rs @@ -0,0 +1,260 @@ +macro_rules! headers_stream_dependency { + (1) => { + StreamDependency::new(StreamId::zero(), 41, false) + }; + (2) => { + StreamDependency::new(StreamId::from(13), 41, false) + }; +} + +macro_rules! pseudo_order { + () => { + PseudoOrder::builder() + .extend([ + PseudoId::Method, + PseudoId::Path, + PseudoId::Authority, + PseudoId::Scheme, + ]) + .build() + }; +} + +macro_rules! settings_order { + () => { + SettingsOrder::builder() + .extend([ + SettingId::HeaderTableSize, + SettingId::EnablePush, + SettingId::MaxConcurrentStreams, + SettingId::InitialWindowSize, + SettingId::MaxFrameSize, + SettingId::MaxHeaderListSize, + SettingId::EnableConnectProtocol, + SettingId::NoRfc7540Priorities, + ]) + .build() + }; +} + +macro_rules! http2_options { + (@base $builder:expr) => { + $builder + .initial_window_size(131072) + .max_frame_size(16384) + .initial_connection_window_size(12517377 + 65535) + .headers_pseudo_order(pseudo_order!()) + .settings_order(settings_order!()) + }; + + (1) => { + http2_options!(@base Http2Options::builder()) + .initial_stream_id(3) + .header_table_size(65536) + .enable_push(false) + .headers_stream_dependency(headers_stream_dependency!(1)) + .build() + }; + (2) => { + http2_options!(@base Http2Options::builder()) + .initial_stream_id(15) + .header_table_size(65536) + .headers_stream_dependency(headers_stream_dependency!(2)) + .priorities( + Priorities::builder() + .extend([ + Priority::new( + StreamId::from(3), + StreamDependency::new(StreamId::zero(), 200, false), + ), + Priority::new( + StreamId::from(5), + StreamDependency::new(StreamId::zero(), 100, false), + ), + Priority::new( + StreamId::from(7), + StreamDependency::new(StreamId::zero(), 0, false), + ), + Priority::new( + StreamId::from(9), + StreamDependency::new(StreamId::from(7), 0, false), + ), + Priority::new( + StreamId::from(11), + StreamDependency::new(StreamId::from(3), 0, false), + ), + Priority::new( + StreamId::from(13), + StreamDependency::new(StreamId::zero(), 240, false), + ), + ]) + .build(), + ) + .build() + }; + (3) => { + http2_options!(@base Http2Options::builder()) + .initial_stream_id(3) + .header_table_size(65536) + .enable_push(false) + .max_concurrent_streams(0) + .headers_stream_dependency(headers_stream_dependency!(1)) + .build() + }; + (4) => { + http2_options!(@base Http2Options::builder()) + .initial_stream_id(3) + .header_table_size(4096) + .enable_push(false) + .initial_window_size(32768) + .headers_stream_dependency(headers_stream_dependency!(1)) + .build() + }; +} + +macro_rules! tls_options { + (@build $builder:expr) => { + $builder.build().into() + }; + + (@base $builder:expr, $cipher_list:expr, $curves:expr) => { + $builder + .cipher_list($cipher_list) + .curves_list($curves) + }; + + (1, $cipher_list:expr, $curves:expr) => { + tls_options!(@build tls_options!(@base FirefoxTlsConfig::builder(), $cipher_list, $curves) + .enable_ech_grease(true) + .pre_shared_key(true) + .psk_skip_session_tickets(true) + .key_shares_limit(3) + .certificate_compression_algorithms(CERT_COMPRESSION_ALGORITHM)) + }; + (2, $cipher_list:expr, $curves:expr) => { + tls_options!(@build tls_options!(@base FirefoxTlsConfig::builder(), $cipher_list, $curves) + .key_shares_limit(2)) + }; + (3, $cipher_list:expr, $curves:expr) => { + tls_options!(@build tls_options!(@base FirefoxTlsConfig::builder(), $cipher_list, $curves) + .session_ticket(false) + .enable_ech_grease(true) + .psk_dhe_ke(false) + .key_shares_limit(2)) + }; + (4, $cipher_list:expr, $curves:expr) => { + tls_options!(@build tls_options!(@base FirefoxTlsConfig::builder(), $cipher_list, $curves) + .enable_ech_grease(true) + .enable_signed_cert_timestamps(true) + .session_ticket(true) + .pre_shared_key(true) + .psk_skip_session_tickets(true) + .key_shares_limit(3) + .certificate_compression_algorithms(CERT_COMPRESSION_ALGORITHM)) + }; + (5, $cipher_list:expr, $curves:expr) => { + tls_options!(@build tls_options!(@base FirefoxTlsConfig::builder(), $cipher_list, $curves) + .enable_ech_grease(true) + .pre_shared_key(true) + .psk_skip_session_tickets(true) + .key_shares_limit(2) + .certificate_compression_algorithms(CERT_COMPRESSION_ALGORITHM)) + }; + (6, $cipher_list:expr, $curves:expr) => { + tls_options!(@build tls_options!(@base FirefoxTlsConfig::builder(), $cipher_list, $curves) + .enable_ech_grease(true) + .enable_signed_cert_timestamps(true) + .session_ticket(false) + .psk_dhe_ke(false) + .key_shares_limit(3) + .certificate_compression_algorithms(CERT_COMPRESSION_ALGORITHM)) + }; +} + +macro_rules! mod_generator { + ( + $mod_name:ident, + $tls_options:expr, + $http2_options:expr, + $header_initializer:ident, + [($default_os:ident, $default_ua:tt) $(, ($other_os:ident, $other_ua:tt))*] + ) => { + pub(crate) mod $mod_name { + use super::*; + + #[inline(always)] + pub fn emulation(option: EmulationOption) -> Emulation { + let default_headers = if !option.skip_headers { + #[allow(unreachable_patterns)] + let default_headers = match option.emulation_os { + $( + EmulationOS::$other_os => { + $header_initializer($other_ua) + } + ),* + _ => { + $header_initializer($default_ua) + } + }; + + Some(default_headers) + } else { + None + }; + + build_emulation(option, default_headers) + } + + #[inline(always)] + pub fn build_emulation( + option: EmulationOption, + default_headers: Option + ) -> Emulation { + let mut builder = Emulation::builder().tls_options($tls_options); + + if !option.skip_http2 { + builder = builder.http2_options($http2_options); + } + + if let Some(headers) = default_headers { + builder = builder.headers(headers); + } + + builder.build() + } + } + }; + ( + $mod_name:ident, + $build_emulation:expr, + $header_initializer:ident, + [($default_os:ident, $default_ua:tt) $(, ($other_os:ident, $other_ua:tt))*] + ) => { + pub(crate) mod $mod_name { + use super::*; + + #[inline(always)] + pub fn emulation(option: EmulationOption) -> Emulation { + let default_headers = if !option.skip_headers { + #[allow(unreachable_patterns)] + let default_headers = match option.emulation_os { + $( + EmulationOS::$other_os => { + $header_initializer($other_ua) + } + ),* + _ => { + $header_initializer($default_ua) + } + }; + + Some(default_headers) + } else { + None + }; + + $build_emulation(option, default_headers) + } + } + }; +} diff --git a/src/emulation/device/firefox/mod.rs b/src/emulation/device/firefox/mod.rs new file mode 100644 index 0000000..9e2ed2f --- /dev/null +++ b/src/emulation/device/firefox/mod.rs @@ -0,0 +1,237 @@ +#[macro_use] +mod macros; +mod header; +mod tls; + +use header::*; +use tls::*; + +use super::{emulation_imports::*, http2_imports::*, *}; + +mod_generator!( + ff109, + tls_options!(2, CIPHER_LIST_1, CURVES_1), + http2_options!(2), + header_initializer, + [ + ( + Windows, + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0" + ), + ( + MacOS, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_17; rv:109.0) Gecko/20000101 Firefox/109.0" + ), + ( + Android, + "Mozilla/5.0 (Android 13; Mobile; rv:109.0) Gecko/109.0 Firefox/109.0" + ), + ( + Linux, + "Mozilla/5.0 (X11; Linux i686; rv:109.0) Gecko/20100101 Firefox/109.0" + ), + ( + IOS, + "Mozilla/5.0 (iPad; CPU OS 13_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/109.0 Mobile/15E148 Safari/605.1.15" + ) + ] +); + +mod_generator!( + ff117, + ff109::build_emulation, + header_initializer, + [ + ( + Windows, + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:117.0) Gecko/20100101 Firefox/117.0" + ), + ( + MacOS, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_16_1; rv:117.0) Gecko/20010101 Firefox/117.0" + ), + ( + Android, + "Mozilla/5.0 (Android 13; Mobile; rv:117.0) Gecko/117.0 Firefox/117.0" + ), + ( + Linux, + "Mozilla/5.0 (X11; Linux i686; rv:117.0) Gecko/20100101 Firefox/117.0" + ), + ( + IOS, + "Mozilla/5.0 (iPhone; CPU iPhone OS 14_7_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/117.0 Mobile/15E148 Safari/605.1.15" + ) + ] +); + +mod_generator!( + ff128, + tls_options!(3, CIPHER_LIST_2, CURVES_1), + http2_options!(3), + header_initializer_with_zstd, + [ + ( + MacOs, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:128.0) Gecko/20100101 Firefox/128.0" + ), + ( + Windows, + "Mozilla/5.0 (Windows NT 10.0; rv:128.0) Gecko/20100101 Firefox/128.0" + ), + ( + Android, + "Mozilla/5.0 (Android 13; Mobile; rv:128.0) Gecko/128.0 Firefox/128.0" + ), + ( + IOS, + "Mozilla/5.0 (iPhone; CPU iPhone OS 17_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/128.0 Mobile/15E148 Safari/605.1.15" + ), + ( + Linux, + "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0" + ) + ] +); + +mod_generator!( + ff133, + tls_options!(1, CIPHER_LIST_1, CURVES_2), + http2_options!(1), + header_initializer_with_zstd, + [ + ( + MacOs, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0" + ), + ( + Android, + "Mozilla/5.0 (Android 13; Mobile; rv:133.0) Gecko/133.0 Firefox/133.0" + ), + ( + Windows, + "Mozilla/5.0 (Windows NT 10.0; rv:133.0) Gecko/20100101 Firefox/133.0" + ), + ( + Linux, + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0" + ), + ( + IOS, + "Mozilla/5.0 (iPhone; CPU iPhone OS 18_2_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/133.4 Mobile/15E148 Safari/605.1.15" + ) + ] +); + +mod_generator!( + ff135, + tls_options!(4, CIPHER_LIST_1, CURVES_2), + http2_options!(1), + header_initializer_with_zstd, + [ + ( + MacOS, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:135.0) Gecko/20100101 Firefox/135.0" + ), + ( + Windows, + "Mozilla/5.0 (Windows NT 10.0; rv:135.0) Gecko/20100101 Firefox/135.0" + ), + ( + Linux, + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:135.0) Gecko/20100101 Firefox/135.0" + ) + ] +); + +mod_generator!( + ff_private_135, + tls_options!(6, CIPHER_LIST_1, CURVES_2), + http2_options!(1), + header_initializer_with_zstd, + [ + ( + MacOS, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:135.0) Gecko/20100101 Firefox/135.0" + ), + ( + Windows, + "Mozilla/5.0 (Windows NT 10.0; rv:135.0) Gecko/20100101 Firefox/135.0" + ), + ( + Linux, + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:135.0) Gecko/20100101 Firefox/135.0" + ) + ] +); + +mod_generator!( + ff_android_135, + tls_options!(5, CIPHER_LIST_1, CURVES_1), + http2_options!(4), + header_initializer_with_zstd, + [( + Android, + "Mozilla/5.0 (Android 13; Mobile; rv:135.0) Gecko/135.0 Firefox/135.0" + )] +); + +mod_generator!( + ff136, + ff135::build_emulation, + header_initializer_with_zstd, + [ + ( + MacOS, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:136.0) Gecko/20100101 Firefox/136.0" + ), + ( + Windows, + "Mozilla/5.0 (Windows NT 10.0; rv:136.0) Gecko/20100101 Firefox/136.0" + ), + ( + Linux, + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0" + ) + ] +); + +mod_generator!( + ff_private_136, + ff_private_135::build_emulation, + header_initializer_with_zstd, + [ + ( + MacOS, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:136.0) Gecko/20100101 Firefox/136.0" + ), + ( + Windows, + "Mozilla/5.0 (Windows NT 10.0; rv:136.0) Gecko/20100101 Firefox/136.0" + ), + ( + Linux, + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0" + ) + ] +); + +mod_generator!( + ff139, + ff135::build_emulation, + header_initializer_with_zstd, + [ + ( + MacOS, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0" + ), + ( + Windows, + "Mozilla/5.0 (Windows NT 10.0; rv:136.0) Gecko/20100101 Firefox/139.0" + ), + ( + Linux, + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/139.0" + ) + ] +); diff --git a/src/emulation/device/firefox/tls.rs b/src/emulation/device/firefox/tls.rs new file mode 100644 index 0000000..ed69e6d --- /dev/null +++ b/src/emulation/device/firefox/tls.rs @@ -0,0 +1,188 @@ +use super::tls_imports::*; + +pub const CURVES_1: &str = join!( + ":", + "X25519", + "P-256", + "P-384", + "P-521", + "ffdhe2048", + "ffdhe3072" +); +pub const CURVES_2: &str = join!( + ":", + "X25519MLKEM768", + "X25519", + "P-256", + "P-384", + "P-521", + "ffdhe2048", + "ffdhe3072" +); + +pub const CIPHER_LIST_1: &str = join!( + ":", + "TLS_AES_128_GCM_SHA256", + "TLS_CHACHA20_POLY1305_SHA256", + "TLS_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA" +); +pub const CIPHER_LIST_2: &str = join!( + ":", + "TLS_AES_128_GCM_SHA256", + "TLS_CHACHA20_POLY1305_SHA256", + "TLS_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA" +); + +pub const SIGALGS_LIST: &str = join!( + ":", + "ecdsa_secp256r1_sha256", + "ecdsa_secp384r1_sha384", + "ecdsa_secp521r1_sha512", + "rsa_pss_rsae_sha256", + "rsa_pss_rsae_sha384", + "rsa_pss_rsae_sha512", + "rsa_pkcs1_sha256", + "rsa_pkcs1_sha384", + "rsa_pkcs1_sha512", + "ecdsa_sha1", + "rsa_pkcs1_sha1" +); + +pub const CERT_COMPRESSION_ALGORITHM: &[CertificateCompressionAlgorithm] = &[ + CertificateCompressionAlgorithm::ZLIB, + CertificateCompressionAlgorithm::BROTLI, + CertificateCompressionAlgorithm::ZSTD, +]; + +pub const DELEGATED_CREDENTIALS: &str = join!( + ":", + "ecdsa_secp256r1_sha256", + "ecdsa_secp384r1_sha384", + "ecdsa_secp521r1_sha512", + "ecdsa_sha1" +); + +pub const RECORD_SIZE_LIMIT: u16 = 0x4001; + +pub const EXTENSION_PERMUTATION_INDICES: &[ExtensionType] = &[ + ExtensionType::SERVER_NAME, + ExtensionType::EXTENDED_MASTER_SECRET, + ExtensionType::RENEGOTIATE, + ExtensionType::SUPPORTED_GROUPS, + ExtensionType::EC_POINT_FORMATS, + ExtensionType::SESSION_TICKET, + ExtensionType::APPLICATION_LAYER_PROTOCOL_NEGOTIATION, + ExtensionType::STATUS_REQUEST, + ExtensionType::DELEGATED_CREDENTIAL, + ExtensionType::CERTIFICATE_TIMESTAMP, + ExtensionType::KEY_SHARE, + ExtensionType::SUPPORTED_VERSIONS, + ExtensionType::SIGNATURE_ALGORITHMS, + ExtensionType::PSK_KEY_EXCHANGE_MODES, + ExtensionType::RECORD_SIZE_LIMIT, + ExtensionType::CERT_COMPRESSION, + ExtensionType::ENCRYPTED_CLIENT_HELLO, +]; + +#[derive(TypedBuilder)] +pub struct FirefoxTlsConfig { + #[builder(default = SIGALGS_LIST)] + sigalgs_list: &'static str, + + #[builder(setter(into))] + cipher_list: &'static str, + + #[builder(setter(into))] + curves_list: &'static str, + + #[builder(default = true)] + session_ticket: bool, + + #[builder(default = false, setter(into))] + enable_ech_grease: bool, + + #[builder(default = false, setter(into))] + enable_signed_cert_timestamps: bool, + + #[builder(default = false, setter(into))] + pre_shared_key: bool, + + #[builder(default = false, setter(into))] + psk_skip_session_tickets: bool, + + #[builder(default = DELEGATED_CREDENTIALS, setter(into))] + delegated_credentials: &'static str, + + #[builder(default = RECORD_SIZE_LIMIT, setter(into))] + record_size_limit: u16, + + #[builder(default, setter(into))] + key_shares_limit: Option, + + #[builder(default = true, setter(into))] + psk_dhe_ke: bool, + + #[builder(default, setter(into))] + certificate_compression_algorithms: Option<&'static [CertificateCompressionAlgorithm]>, + + #[builder(default = EXTENSION_PERMUTATION_INDICES, setter(into))] + extension_permutation: &'static [ExtensionType], +} + +impl From for TlsOptions { + fn from(val: FirefoxTlsConfig) -> Self { + let mut builder = TlsOptions::builder() + .curves_list(val.curves_list) + .sigalgs_list(val.sigalgs_list) + .cipher_list(val.cipher_list) + .session_ticket(val.session_ticket) + .delegated_credentials(val.delegated_credentials) + .record_size_limit(val.record_size_limit) + .enable_ocsp_stapling(true) + .enable_ech_grease(val.enable_ech_grease) + .enable_signed_cert_timestamps(val.enable_signed_cert_timestamps) + .alpn_protocols([AlpnProtocol::HTTP2, AlpnProtocol::HTTP1]) + .min_tls_version(TlsVersion::TLS_1_2) + .max_tls_version(TlsVersion::TLS_1_3) + .key_shares_limit(val.key_shares_limit) + .pre_shared_key(val.pre_shared_key) + .psk_skip_session_ticket(val.psk_skip_session_tickets) + .psk_dhe_ke(val.psk_dhe_ke) + .prefer_chacha20(true) + .extension_permutation(val.extension_permutation) + .aes_hw_override(true) + .random_aes_hw_override(true); + + if let Some(cert_compression_algorithms) = val.certificate_compression_algorithms { + builder = builder.certificate_compression_algorithms(cert_compression_algorithms) + } + + builder.build() + } +} diff --git a/src/emulation/device/okhttp/macros.rs b/src/emulation/device/okhttp/macros.rs new file mode 100644 index 0000000..940ea84 --- /dev/null +++ b/src/emulation/device/okhttp/macros.rs @@ -0,0 +1,11 @@ +macro_rules! mod_generator { + ($mod_name:ident, $cipher:expr, $ua:expr) => { + pub(crate) mod $mod_name { + use super::*; + #[inline(always)] + pub fn emulation(option: EmulationOption) -> Emulation { + build_emulation(option, $cipher, $ua) + } + } + }; +} diff --git a/src/emulation/device/okhttp.rs b/src/emulation/device/okhttp/mod.rs similarity index 85% rename from src/emulation/device/okhttp.rs rename to src/emulation/device/okhttp/mod.rs index f2c6bb5..648ddae 100644 --- a/src/emulation/device/okhttp.rs +++ b/src/emulation/device/okhttp/mod.rs @@ -1,3 +1,6 @@ +#[macro_use] +mod macros; + use super::{emulation_imports::*, http2_imports::*, tls_imports::*}; const CURVES: &str = join!(":", "X25519", "P-256", "P-384"); @@ -35,17 +38,41 @@ const CIPHER_LIST: &str = join!( "TLS_RSA_WITH_3DES_EDE_CBC_SHA" ); +#[derive(TypedBuilder)] +struct OkHttpTlsConfig { + #[builder(default = CURVES)] + curves: &'static str, + + #[builder(default = SIGALGS_LIST)] + sigalgs_list: &'static str, + + cipher_list: &'static str, +} + +impl From for TlsOptions { + fn from(val: OkHttpTlsConfig) -> Self { + TlsOptions::builder() + .enable_ocsp_stapling(true) + .curves_list(val.curves) + .sigalgs_list(val.sigalgs_list) + .cipher_list(val.cipher_list) + .min_tls_version(TlsVersion::TLS_1_2) + .max_tls_version(TlsVersion::TLS_1_3) + .build() + } +} + fn build_emulation( option: EmulationOption, cipher_list: &'static str, - default_headers: Option, + user_agent: &'static str, ) -> Emulation { - let tls_opts = OkHttpTlsConfig::builder() - .cipher_list(cipher_list) - .build() - .into(); - - let mut builder = Emulation::builder().tls_options(tls_opts); + let mut builder = Emulation::builder().tls_options( + OkHttpTlsConfig::builder() + .cipher_list(cipher_list) + .build() + .into(), + ); if !option.skip_http2 { let settings_order = SettingsOrder::builder() @@ -84,69 +111,22 @@ fn build_emulation( builder = builder.http2_options(http2_opts); } - if let Some(headers) = default_headers { + if !option.skip_headers { + let mut headers = HeaderMap::new(); + headers.insert(ACCEPT, HeaderValue::from_static("*/*")); + headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.9")); + headers.insert(USER_AGENT, HeaderValue::from_static(user_agent)); + #[cfg(all(feature = "gzip", feature = "deflate", feature = "brotli"))] + headers.insert( + ACCEPT_ENCODING, + HeaderValue::from_static("gzip, deflate, br"), + ); builder = builder.headers(headers); } builder.build() } -#[derive(TypedBuilder)] -struct OkHttpTlsConfig { - #[builder(default = CURVES)] - curves: &'static str, - - #[builder(default = SIGALGS_LIST)] - sigalgs_list: &'static str, - - cipher_list: &'static str, -} - -impl From for TlsOptions { - fn from(val: OkHttpTlsConfig) -> Self { - TlsOptions::builder() - .enable_ocsp_stapling(true) - .curves_list(val.curves) - .sigalgs_list(val.sigalgs_list) - .cipher_list(val.cipher_list) - .min_tls_version(TlsVersion::TLS_1_2) - .max_tls_version(TlsVersion::TLS_1_3) - .build() - } -} - -fn header_initializer(ua: &'static str) -> HeaderMap { - let mut headers = HeaderMap::new(); - headers.insert(ACCEPT, HeaderValue::from_static("*/*")); - headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.9")); - headers.insert(USER_AGENT, HeaderValue::from_static(ua)); - #[cfg(all(feature = "gzip", feature = "deflate", feature = "brotli"))] - headers.insert( - ACCEPT_ENCODING, - HeaderValue::from_static("gzip, deflate, br"), - ); - headers -} - -macro_rules! mod_generator { - ($mod_name:ident, $cipher_list:expr, $ua:expr) => { - pub(crate) mod $mod_name { - use super::*; - - #[inline(always)] - pub fn emulation(option: EmulationOption) -> Emulation { - let default_headers = if !option.skip_headers { - Some(header_initializer($ua)) - } else { - None - }; - - build_emulation(option, $cipher_list, default_headers) - } - } - }; -} - mod_generator!( okhttp3_9, join!( diff --git a/src/emulation/device/opera.rs b/src/emulation/device/opera.rs deleted file mode 100644 index df69829..0000000 --- a/src/emulation/device/opera.rs +++ /dev/null @@ -1,359 +0,0 @@ -use header::*; -use tls::*; - -use super::{emulation_imports::*, http2_imports::*, *}; - -macro_rules! headers_stream_dependency { - () => { - StreamDependency::new(StreamId::zero(), 255, true) - }; -} - -macro_rules! pseudo_order { - () => { - PseudoOrder::builder() - .extend([ - PseudoId::Method, - PseudoId::Authority, - PseudoId::Scheme, - PseudoId::Path, - ]) - .build() - }; -} - -macro_rules! settings_order { - () => { - SettingsOrder::builder() - .extend([ - SettingId::HeaderTableSize, - SettingId::EnablePush, - SettingId::MaxConcurrentStreams, - SettingId::InitialWindowSize, - SettingId::MaxFrameSize, - SettingId::MaxHeaderListSize, - SettingId::EnableConnectProtocol, - SettingId::NoRfc7540Priorities, - ]) - .build() - }; -} - -macro_rules! tls_options { - ($curves:expr) => { - OperaTlsConfig::builder() - .curves($curves) - .permute_extensions(true) - .pre_shared_key(true) - .enable_ech_grease(true) - .build() - .into() - }; -} - -macro_rules! http2_options { - () => { - Http2Options::builder() - .initial_window_size(6291456) - .initial_connection_window_size(15728640) - .max_header_list_size(262144) - .header_table_size(65536) - .enable_push(false) - .headers_stream_dependency(headers_stream_dependency!()) - .headers_pseudo_order(pseudo_order!()) - .settings_order(settings_order!()) - .build() - }; -} - -mod header { - use super::*; - - #[inline] - pub fn header_initializer_with_zstd_priority( - sec_ch_ua: &'static str, - ua: &'static str, - emulation_os: EmulationOS, - ) -> HeaderMap { - let mut headers = HeaderMap::new(); - header_chrome_sec_ch_ua!( - headers, - sec_ch_ua, - emulation_os.platform(), - emulation_os.is_mobile() - ); - header_chrome_ua!(headers, ua); - - headers.insert(ACCEPT, HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")); - #[cfg(all( - feature = "gzip", - feature = "deflate", - feature = "brotli", - feature = "zstd" - ))] - headers.insert( - ACCEPT_ENCODING, - HeaderValue::from_static("gzip, deflate, br, zstd"), - ); - headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.9")); - headers.insert( - HeaderName::from_static("priority"), - HeaderValue::from_static("u=0, i"), - ); - headers - } -} -mod tls { - use super::tls_imports::*; - - pub const CURVES: &'static str = join!(":", "X25519MLKEM768", "X25519", "P-256", "P-384"); - - pub const CIPHER_LIST: &str = join!( - ":", - "TLS_AES_128_GCM_SHA256", - "TLS_AES_256_GCM_SHA384", - "TLS_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "TLS_RSA_WITH_AES_128_GCM_SHA256", - "TLS_RSA_WITH_AES_256_GCM_SHA384", - "TLS_RSA_WITH_AES_128_CBC_SHA", - "TLS_RSA_WITH_AES_256_CBC_SHA" - ); - - pub const SIGALGS_LIST: &str = join!( - ":", - "ecdsa_secp256r1_sha256", - "rsa_pss_rsae_sha256", - "rsa_pkcs1_sha256", - "ecdsa_secp384r1_sha384", - "rsa_pss_rsae_sha384", - "rsa_pkcs1_sha384", - "rsa_pss_rsae_sha512", - "rsa_pkcs1_sha512" - ); - - pub const CERT_COMPRESSION_ALGORITHM: &[CertificateCompressionAlgorithm] = - &[CertificateCompressionAlgorithm::BROTLI]; - - #[derive(TypedBuilder)] - pub struct OperaTlsConfig { - #[builder(default = CURVES)] - curves: &'static str, - - #[builder(default = SIGALGS_LIST)] - sigalgs_list: &'static str, - - #[builder(default = CIPHER_LIST)] - cipher_list: &'static str, - - #[builder(default = AlpsProtocol::HTTP2, setter(into))] - alps_protos: AlpsProtocol, - - #[builder(default = false)] - alps_use_new_codepoint: bool, - - #[builder(default = false, setter(into))] - enable_ech_grease: bool, - - #[builder(default = false, setter(into))] - permute_extensions: bool, - - #[builder(default = false, setter(into))] - pre_shared_key: bool, - } - - impl From for TlsOptions { - fn from(val: OperaTlsConfig) -> Self { - TlsOptions::builder() - .grease_enabled(true) - .enable_ocsp_stapling(true) - .enable_signed_cert_timestamps(true) - .curves_list(val.curves) - .sigalgs_list(val.sigalgs_list) - .cipher_list(val.cipher_list) - .min_tls_version(TlsVersion::TLS_1_2) - .max_tls_version(TlsVersion::TLS_1_3) - .permute_extensions(val.permute_extensions) - .pre_shared_key(val.pre_shared_key) - .enable_ech_grease(val.enable_ech_grease) - .alps_protocols([val.alps_protos]) - .alps_use_new_codepoint(val.alps_use_new_codepoint) - .certificate_compression_algorithms(CERT_COMPRESSION_ALGORITHM) - .build() - } - } -} - -macro_rules! mod_generator { - ( - $mod_name:ident, - $tls_options:expr, - $http2_options:expr, - $header_initializer:ident, - [($default_os:ident, $default_sec_ch_ua:tt, $default_ua:tt) $(, ($other_os:ident, $other_sec_ch_ua:tt, $other_ua:tt))*] - ) => { - pub(crate) mod $mod_name { - use super::*; - - #[inline(always)] - pub fn emulation(option: EmulationOption) -> Emulation { - let default_headers = if !option.skip_headers { - #[allow(unreachable_patterns)] - let default_headers = match option.emulation_os { - $( - EmulationOS::$other_os => $header_initializer( - $other_sec_ch_ua, - $other_ua, - option.emulation_os, - ), - )* - _ => $header_initializer( - $default_sec_ch_ua, - $default_ua, - EmulationOS::$default_os, - ), - }; - Some(default_headers) - } else { - None - }; - - build_emulation(option, default_headers) - } - - #[inline(always)] - pub fn build_emulation( - option: EmulationOption, - default_headers: Option - ) -> Emulation { - let mut builder = Emulation::builder().tls_options($tls_options); - - if !option.skip_http2 { - builder = builder.http2_options($http2_options); - } - - if let Some(headers) = default_headers { - builder = builder.headers(headers); - } - - builder.build() - } - } - }; - ( - $mod_name:ident, - $build_emulation:expr, - $header_initializer:ident, - [($default_os:ident, $default_sec_ch_ua:tt, $default_ua:tt) $(, ($other_os:ident, $other_sec_ch_ua:tt, $other_ua:tt))*] - ) => { - pub(crate) mod $mod_name { - use super::*; - - #[inline(always)] - pub fn emulation(option: EmulationOption) -> Emulation { - let default_headers = if !option.skip_headers { - #[allow(unreachable_patterns)] - let default_headers = match option.emulation_os { - $( - EmulationOS::$other_os => $header_initializer( - $other_sec_ch_ua, - $other_ua, - option.emulation_os, - ), - )* - _ => $header_initializer( - $default_sec_ch_ua, - $default_ua, - EmulationOS::$default_os, - ), - }; - Some(default_headers) - } else { - None - }; - - $build_emulation(option, default_headers) - } - } - }; -} - -mod_generator!( - opera116, - tls_options!(CURVES), - http2_options!(), - header_initializer_with_zstd_priority, - [ - ( - MacOS, - r#""Opera";v="116", "Chromium";v="131", "Not_A Brand";v="24""#, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 OPR/116.0.0.0" - ), - ( - Windows, - r#""Opera";v="116", "Chromium";v="131", "Not_A Brand";v="24""#, - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 OPR/116.0.0.0" - ) - ] -); - -mod_generator!( - opera117, - opera116::build_emulation, - header_initializer_with_zstd_priority, - [ - ( - MacOS, - r#""Not A(Brand";v="8", "Chromium";v="132", "Opera";v="117""#, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 OPR/117.0.0.0" - ), - ( - Windows, - r#""Not A(Brand";v="8", "Chromium";v="132", "Opera";v="117""#, - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 OPR/117.0.0.0" - ) - ] -); - -mod_generator!( - opera118, - opera116::build_emulation, - header_initializer_with_zstd_priority, - [ - ( - MacOS, - r#""Not(A:Brand";v="99", "Opera";v="118", "Chromium";v="133""#, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 OPR/118.0.0.0" - ), - ( - Windows, - r#""Not(A:Brand";v="99", "Opera";v="118", "Chromium";v="133""#, - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 OPR/118.0.0.0" - ) - ] -); - -mod_generator!( - opera119, - opera116::build_emulation, - header_initializer_with_zstd_priority, - [ - ( - MacOS, - r#""Chromium";v="134", "Not:A-Brand";v="24", "Opera";v="119""#, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 OPR/119.0.0.0" - ), - ( - Windows, - r#""Chromium";v="134", "Not:A-Brand";v="24", "Opera";v="119""#, - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 OPR/119.0.0.0" - ) - ] -); diff --git a/src/emulation/device/opera/header.rs b/src/emulation/device/opera/header.rs new file mode 100644 index 0000000..a88a5e0 --- /dev/null +++ b/src/emulation/device/opera/header.rs @@ -0,0 +1,35 @@ +use super::*; + +#[inline] +pub fn header_initializer_with_zstd_priority( + sec_ch_ua: &'static str, + ua: &'static str, + emulation_os: EmulationOS, +) -> HeaderMap { + let mut headers = HeaderMap::new(); + header_chrome_sec_ch_ua!( + headers, + sec_ch_ua, + emulation_os.platform(), + emulation_os.is_mobile() + ); + header_chrome_ua!(headers, ua); + + headers.insert(ACCEPT, HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")); + #[cfg(all( + feature = "gzip", + feature = "deflate", + feature = "brotli", + feature = "zstd" + ))] + headers.insert( + ACCEPT_ENCODING, + HeaderValue::from_static("gzip, deflate, br, zstd"), + ); + headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.9")); + headers.insert( + HeaderName::from_static("priority"), + HeaderValue::from_static("u=0, i"), + ); + headers +} diff --git a/src/emulation/device/opera/macros.rs b/src/emulation/device/opera/macros.rs new file mode 100644 index 0000000..30523ce --- /dev/null +++ b/src/emulation/device/opera/macros.rs @@ -0,0 +1,156 @@ +macro_rules! headers_stream_dependency { + () => { + StreamDependency::new(StreamId::zero(), 255, true) + }; +} + +macro_rules! pseudo_order { + () => { + PseudoOrder::builder() + .extend([ + PseudoId::Method, + PseudoId::Authority, + PseudoId::Scheme, + PseudoId::Path, + ]) + .build() + }; +} + +macro_rules! settings_order { + () => { + SettingsOrder::builder() + .extend([ + SettingId::HeaderTableSize, + SettingId::EnablePush, + SettingId::MaxConcurrentStreams, + SettingId::InitialWindowSize, + SettingId::MaxFrameSize, + SettingId::MaxHeaderListSize, + SettingId::EnableConnectProtocol, + SettingId::NoRfc7540Priorities, + ]) + .build() + }; +} + +macro_rules! tls_options { + ($curves:expr) => { + OperaTlsConfig::builder() + .curves($curves) + .permute_extensions(true) + .pre_shared_key(true) + .enable_ech_grease(true) + .build() + .into() + }; +} + +macro_rules! http2_options { + () => { + Http2Options::builder() + .initial_window_size(6291456) + .initial_connection_window_size(15728640) + .max_header_list_size(262144) + .header_table_size(65536) + .enable_push(false) + .headers_stream_dependency(headers_stream_dependency!()) + .headers_pseudo_order(pseudo_order!()) + .settings_order(settings_order!()) + .build() + }; +} + +macro_rules! mod_generator { + ( + $mod_name:ident, + $tls_options:expr, + $http2_options:expr, + $header_initializer:ident, + [($default_os:ident, $default_sec_ch_ua:tt, $default_ua:tt) $(, ($other_os:ident, $other_sec_ch_ua:tt, $other_ua:tt))*] + ) => { + pub(crate) mod $mod_name { + use super::*; + + #[inline(always)] + pub fn emulation(option: EmulationOption) -> Emulation { + let default_headers = if !option.skip_headers { + #[allow(unreachable_patterns)] + let default_headers = match option.emulation_os { + $( + EmulationOS::$other_os => $header_initializer( + $other_sec_ch_ua, + $other_ua, + option.emulation_os, + ), + )* + _ => $header_initializer( + $default_sec_ch_ua, + $default_ua, + EmulationOS::$default_os, + ), + }; + Some(default_headers) + } else { + None + }; + + build_emulation(option, default_headers) + } + + #[inline(always)] + pub fn build_emulation( + option: EmulationOption, + default_headers: Option + ) -> Emulation { + let mut builder = Emulation::builder().tls_options($tls_options); + + if !option.skip_http2 { + builder = builder.http2_options($http2_options); + } + + if let Some(headers) = default_headers { + builder = builder.headers(headers); + } + + builder.build() + } + } + }; + ( + $mod_name:ident, + $build_emulation:expr, + $header_initializer:ident, + [($default_os:ident, $default_sec_ch_ua:tt, $default_ua:tt) $(, ($other_os:ident, $other_sec_ch_ua:tt, $other_ua:tt))*] + ) => { + pub(crate) mod $mod_name { + use super::*; + + #[inline(always)] + pub fn emulation(option: EmulationOption) -> Emulation { + let default_headers = if !option.skip_headers { + #[allow(unreachable_patterns)] + let default_headers = match option.emulation_os { + $( + EmulationOS::$other_os => $header_initializer( + $other_sec_ch_ua, + $other_ua, + option.emulation_os, + ), + )* + _ => $header_initializer( + $default_sec_ch_ua, + $default_ua, + EmulationOS::$default_os, + ), + }; + Some(default_headers) + } else { + None + }; + + $build_emulation(option, default_headers) + } + } + }; +} diff --git a/src/emulation/device/opera/mod.rs b/src/emulation/device/opera/mod.rs new file mode 100644 index 0000000..3d2d0a9 --- /dev/null +++ b/src/emulation/device/opera/mod.rs @@ -0,0 +1,82 @@ +#[macro_use] +mod macros; +mod header; +mod tls; + +use header::*; +use tls::*; + +use super::{emulation_imports::*, http2_imports::*, *}; + +mod_generator!( + opera116, + tls_options!(CURVES), + http2_options!(), + header_initializer_with_zstd_priority, + [ + ( + MacOS, + r#""Opera";v="116", "Chromium";v="131", "Not_A Brand";v="24""#, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 OPR/116.0.0.0" + ), + ( + Windows, + r#""Opera";v="116", "Chromium";v="131", "Not_A Brand";v="24""#, + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 OPR/116.0.0.0" + ) + ] +); + +mod_generator!( + opera117, + opera116::build_emulation, + header_initializer_with_zstd_priority, + [ + ( + MacOS, + r#""Not A(Brand";v="8", "Chromium";v="132", "Opera";v="117""#, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 OPR/117.0.0.0" + ), + ( + Windows, + r#""Not A(Brand";v="8", "Chromium";v="132", "Opera";v="117""#, + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 OPR/117.0.0.0" + ) + ] +); + +mod_generator!( + opera118, + opera116::build_emulation, + header_initializer_with_zstd_priority, + [ + ( + MacOS, + r#""Not(A:Brand";v="99", "Opera";v="118", "Chromium";v="133""#, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 OPR/118.0.0.0" + ), + ( + Windows, + r#""Not(A:Brand";v="99", "Opera";v="118", "Chromium";v="133""#, + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 OPR/118.0.0.0" + ) + ] +); + +mod_generator!( + opera119, + opera116::build_emulation, + header_initializer_with_zstd_priority, + [ + ( + MacOS, + r#""Chromium";v="134", "Not:A-Brand";v="24", "Opera";v="119""#, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 OPR/119.0.0.0" + ), + ( + Windows, + r#""Chromium";v="134", "Not:A-Brand";v="24", "Opera";v="119""#, + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 OPR/119.0.0.0" + ) + ] +); diff --git a/src/emulation/device/opera/tls.rs b/src/emulation/device/opera/tls.rs new file mode 100644 index 0000000..4b38667 --- /dev/null +++ b/src/emulation/device/opera/tls.rs @@ -0,0 +1,85 @@ +use super::tls_imports::*; + +pub const CURVES: &'static str = join!(":", "X25519MLKEM768", "X25519", "P-256", "P-384"); + +pub const CIPHER_LIST: &str = join!( + ":", + "TLS_AES_128_GCM_SHA256", + "TLS_AES_256_GCM_SHA384", + "TLS_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA" +); + +pub const SIGALGS_LIST: &str = join!( + ":", + "ecdsa_secp256r1_sha256", + "rsa_pss_rsae_sha256", + "rsa_pkcs1_sha256", + "ecdsa_secp384r1_sha384", + "rsa_pss_rsae_sha384", + "rsa_pkcs1_sha384", + "rsa_pss_rsae_sha512", + "rsa_pkcs1_sha512" +); + +pub const CERT_COMPRESSION_ALGORITHM: &[CertificateCompressionAlgorithm] = + &[CertificateCompressionAlgorithm::BROTLI]; + +#[derive(TypedBuilder)] +pub struct OperaTlsConfig { + #[builder(default = CURVES)] + curves: &'static str, + + #[builder(default = SIGALGS_LIST)] + sigalgs_list: &'static str, + + #[builder(default = CIPHER_LIST)] + cipher_list: &'static str, + + #[builder(default = AlpsProtocol::HTTP2, setter(into))] + alps_protos: AlpsProtocol, + + #[builder(default = false)] + alps_use_new_codepoint: bool, + + #[builder(default = false, setter(into))] + enable_ech_grease: bool, + + #[builder(default = false, setter(into))] + permute_extensions: bool, + + #[builder(default = false, setter(into))] + pre_shared_key: bool, +} + +impl From for TlsOptions { + fn from(val: OperaTlsConfig) -> Self { + TlsOptions::builder() + .grease_enabled(true) + .enable_ocsp_stapling(true) + .enable_signed_cert_timestamps(true) + .curves_list(val.curves) + .sigalgs_list(val.sigalgs_list) + .cipher_list(val.cipher_list) + .min_tls_version(TlsVersion::TLS_1_2) + .max_tls_version(TlsVersion::TLS_1_3) + .permute_extensions(val.permute_extensions) + .pre_shared_key(val.pre_shared_key) + .enable_ech_grease(val.enable_ech_grease) + .alps_protocols([val.alps_protos]) + .alps_use_new_codepoint(val.alps_use_new_codepoint) + .certificate_compression_algorithms(CERT_COMPRESSION_ALGORITHM) + .build() + } +} diff --git a/src/emulation/device/safari.rs b/src/emulation/device/safari.rs deleted file mode 100644 index 53f3628..0000000 --- a/src/emulation/device/safari.rs +++ /dev/null @@ -1,534 +0,0 @@ -use header::*; -use tls::*; - -use super::{emulation_imports::*, http2_imports::*, *}; - -macro_rules! headers_stream_dependency { - (1) => { - StreamDependency::new(StreamId::zero(), 255, true) - }; - (2) => { - StreamDependency::new(StreamId::zero(), 255, false) - }; -} - -macro_rules! headers_pseudo_order { - (1) => { - PseudoOrder::builder() - .extend([ - PseudoId::Method, - PseudoId::Scheme, - PseudoId::Path, - PseudoId::Authority, - ]) - .build() - }; - (2) => { - PseudoOrder::builder() - .extend([ - PseudoId::Method, - PseudoId::Scheme, - PseudoId::Authority, - PseudoId::Path, - ]) - .build() - }; -} - -macro_rules! settings_order { - (1) => { - SettingsOrder::builder() - .extend([ - SettingId::HeaderTableSize, - SettingId::EnablePush, - SettingId::InitialWindowSize, - SettingId::MaxConcurrentStreams, - SettingId::MaxFrameSize, - SettingId::MaxHeaderListSize, - SettingId::EnableConnectProtocol, - SettingId::NoRfc7540Priorities, - ]) - .build() - }; - (2) => { - SettingsOrder::builder() - .extend([ - SettingId::HeaderTableSize, - SettingId::EnablePush, - SettingId::MaxConcurrentStreams, - SettingId::InitialWindowSize, - SettingId::MaxFrameSize, - SettingId::MaxHeaderListSize, - SettingId::EnableConnectProtocol, - SettingId::NoRfc7540Priorities, - ]) - .build() - }; -} - -macro_rules! tls_options { - (1, $cipher_list:expr) => { - SafariTlsConfig::builder() - .cipher_list($cipher_list) - .build() - .into() - }; - (2, $cipher_list:expr, $sigalgs_list:expr) => { - SafariTlsConfig::builder() - .cipher_list($cipher_list) - .sigalgs_list($sigalgs_list) - .build() - .into() - }; -} - -macro_rules! http2_options { - (1) => { - Http2Options::builder() - .initial_window_size(2097152) - .initial_connection_window_size(10551295) - .max_concurrent_streams(100) - .headers_stream_dependency(headers_stream_dependency!(1)) - .headers_pseudo_order(headers_pseudo_order!(1)) - .settings_order(settings_order!(1)) - .build() - }; - (2) => { - Http2Options::builder() - .initial_window_size(2097152) - .initial_connection_window_size(10551295) - .max_concurrent_streams(100) - .enable_push(false) - .headers_stream_dependency(headers_stream_dependency!(1)) - .headers_pseudo_order(headers_pseudo_order!(1)) - .settings_order(settings_order!(1)) - .build() - }; - (3) => { - Http2Options::builder() - .initial_window_size(2097152) - .initial_connection_window_size(10485760) - .max_concurrent_streams(100) - .enable_push(false) - .enable_connect_protocol(true) - .no_rfc7540_priorities(true) - .headers_stream_dependency(headers_stream_dependency!(2)) - .headers_pseudo_order(headers_pseudo_order!(2)) - .settings_order(settings_order!(2)) - .build() - }; - (4) => { - Http2Options::builder() - .initial_window_size(4194304) - .initial_connection_window_size(10551295) - .max_concurrent_streams(100) - .headers_stream_dependency(headers_stream_dependency!(1)) - .headers_pseudo_order(headers_pseudo_order!(1)) - .settings_order(settings_order!(1)) - .build() - }; - (5) => { - Http2Options::builder() - .initial_window_size(4194304) - .initial_connection_window_size(10551295) - .max_concurrent_streams(100) - .enable_push(false) - .headers_stream_dependency(headers_stream_dependency!(1)) - .headers_pseudo_order(headers_pseudo_order!(1)) - .settings_order(settings_order!(1)) - .build() - }; - (6) => { - Http2Options::builder() - .initial_window_size(2097152) - .initial_connection_window_size(10485760) - .max_concurrent_streams(100) - .enable_push(false) - .no_rfc7540_priorities(true) - .headers_stream_dependency(headers_stream_dependency!(2)) - .headers_pseudo_order(headers_pseudo_order!(2)) - .settings_order(settings_order!(2)) - .build() - }; -} - -mod header { - use super::*; - - #[inline] - pub fn header_initializer_for_15(ua: &'static str) -> HeaderMap { - let mut headers = HeaderMap::new(); - headers.insert(USER_AGENT, HeaderValue::from_static(ua)); - headers.insert( - ACCEPT, - HeaderValue::from_static( - "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", - ), - ); - headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.9")); - #[cfg(all(feature = "gzip", feature = "deflate", feature = "brotli"))] - headers.insert( - ACCEPT_ENCODING, - HeaderValue::from_static("gzip, deflate, br"), - ); - headers - } - - #[inline] - pub fn header_initializer_for_16_17(ua: &'static str) -> HeaderMap { - let mut headers = HeaderMap::new(); - headers.insert( - ACCEPT, - HeaderValue::from_static( - "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", - ), - ); - headers.insert("sec-fetch-site", HeaderValue::from_static("none")); - #[cfg(all(feature = "gzip", feature = "deflate", feature = "brotli"))] - headers.insert( - ACCEPT_ENCODING, - HeaderValue::from_static("gzip, deflate, br"), - ); - headers.insert("sec-fetch-mode", HeaderValue::from_static("navigate")); - headers.insert(USER_AGENT, HeaderValue::from_static(ua)); - headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.9")); - headers.insert("sec-fetch-dest", HeaderValue::from_static("document")); - headers - } - - #[inline] - pub fn header_initializer_for_18(ua: &'static str) -> HeaderMap { - let mut headers = HeaderMap::new(); - headers.insert("sec-fetch-dest", HeaderValue::from_static("document")); - headers.insert(USER_AGENT, HeaderValue::from_static(ua)); - headers.insert( - ACCEPT, - HeaderValue::from_static( - "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", - ), - ); - headers.insert("sec-fetch-site", HeaderValue::from_static("none")); - headers.insert("sec-fetch-mode", HeaderValue::from_static("navigate")); - headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.9")); - headers.insert("priority", HeaderValue::from_static("u=0, i")); - #[cfg(all(feature = "gzip", feature = "deflate", feature = "brotli"))] - headers.insert( - ACCEPT_ENCODING, - HeaderValue::from_static("gzip, deflate, br"), - ); - headers - } -} - -mod tls { - use super::tls_imports::*; - - pub const CURVES: &str = join!(":", "X25519", "P-256", "P-384", "P-521"); - - pub const CIPHER_LIST_1: &str = join!( - ":", - "TLS_AES_128_GCM_SHA256", - "TLS_AES_256_GCM_SHA384", - "TLS_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "TLS_RSA_WITH_AES_256_GCM_SHA384", - "TLS_RSA_WITH_AES_128_GCM_SHA256", - "TLS_RSA_WITH_AES_256_CBC_SHA256", - "TLS_RSA_WITH_AES_128_CBC_SHA256", - "TLS_RSA_WITH_AES_256_CBC_SHA", - "TLS_RSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", - "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", - "TLS_RSA_WITH_3DES_EDE_CBC_SHA" - ); - pub const CIPHER_LIST_2: &str = join!( - ":", - "TLS_AES_128_GCM_SHA256", - "TLS_AES_256_GCM_SHA384", - "TLS_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "TLS_RSA_WITH_AES_256_GCM_SHA384", - "TLS_RSA_WITH_AES_128_GCM_SHA256", - "TLS_RSA_WITH_AES_256_CBC_SHA", - "TLS_RSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", - "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", - "TLS_RSA_WITH_3DES_EDE_CBC_SHA" - ); - - pub const SIGALGS_LIST: &str = join!( - ":", - "ecdsa_secp256r1_sha256", - "rsa_pss_rsae_sha256", - "rsa_pkcs1_sha256", - "ecdsa_secp384r1_sha384", - "ecdsa_sha1", - "rsa_pss_rsae_sha384", - "rsa_pss_rsae_sha384", - "rsa_pkcs1_sha384", - "rsa_pss_rsae_sha512", - "rsa_pkcs1_sha512", - "rsa_pkcs1_sha1" - ); - - pub const NEW_SIGALGS_LIST: &str = join!( - ":", - "ecdsa_secp256r1_sha256", - "rsa_pss_rsae_sha256", - "rsa_pkcs1_sha256", - "ecdsa_secp384r1_sha384", - "rsa_pss_rsae_sha384", - "rsa_pss_rsae_sha384", - "rsa_pkcs1_sha384", - "rsa_pss_rsae_sha512", - "rsa_pkcs1_sha512", - "rsa_pkcs1_sha1" - ); - - pub const CERT_COMPRESSION_ALGORITHM: &[CertificateCompressionAlgorithm] = - &[CertificateCompressionAlgorithm::ZLIB]; - - #[derive(TypedBuilder)] - pub struct SafariTlsConfig { - #[builder(default = CURVES)] - curves: &'static str, - - #[builder(default = SIGALGS_LIST)] - sigalgs_list: &'static str, - - cipher_list: &'static str, - } - - impl From for TlsOptions { - fn from(val: SafariTlsConfig) -> Self { - TlsOptions::builder() - .session_ticket(false) - .grease_enabled(true) - .enable_ocsp_stapling(true) - .enable_signed_cert_timestamps(true) - .curves_list(val.curves) - .sigalgs_list(val.sigalgs_list) - .cipher_list(val.cipher_list) - .min_tls_version(TlsVersion::TLS_1_0) - .certificate_compression_algorithms(CERT_COMPRESSION_ALGORITHM) - .build() - } - } -} - -macro_rules! mod_generator { - ($mod_name:ident, $tls_options:expr, $http2_options:expr, $header_initializer:ident, $ua:expr) => { - pub(crate) mod $mod_name { - use super::*; - - #[inline(always)] - pub fn emulation(option: EmulationOption) -> Emulation { - let default_headers = if !option.skip_headers { - Some($header_initializer($ua)) - } else { - None - }; - - build_emulation(option, default_headers) - } - - #[inline(always)] - pub fn build_emulation( - option: EmulationOption, - default_headers: Option, - ) -> Emulation { - let mut builder = Emulation::builder().tls_options($tls_options); - - if !option.skip_http2 { - builder = builder.http2_options($http2_options); - } - - if let Some(headers) = default_headers { - builder = builder.headers(headers); - } - - builder.build() - } - } - }; - - ($mod_name:ident, $build_emulation:expr, $header_initializer:ident, $ua:expr) => { - pub(crate) mod $mod_name { - use super::*; - - #[inline(always)] - pub fn emulation(option: EmulationOption) -> Emulation { - let default_headers = if !option.skip_headers { - Some($header_initializer($ua)) - } else { - None - }; - - $build_emulation(option, default_headers) - } - } - }; -} - -mod_generator!( - safari15_3, - tls_options!(1, CIPHER_LIST_1), - http2_options!(4), - header_initializer_for_15, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.3 Safari/605.1.15" -); - -mod_generator!( - safari15_5, - safari15_3::build_emulation, - header_initializer_for_15, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Safari/605.1.15" -); - -mod_generator!( - safari15_6_1, - tls_options!(1, CIPHER_LIST_2), - http2_options!(4), - header_initializer_for_15, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6.1 Safari/605.1.15" -); - -mod_generator!( - safari16, - safari15_6_1::build_emulation, - header_initializer_for_16_17, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15" -); - -mod_generator!( - safari16_5, - safari15_6_1::build_emulation, - header_initializer_for_16_17, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Safari/605.1.15" -); - -mod_generator!( - safari17_4_1, - safari15_6_1::build_emulation, - header_initializer_for_16_17, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Safari/605.1.15" -); - -mod_generator!( - safari_ios_16_5, - tls_options!(1, CIPHER_LIST_2), - http2_options!(1), - header_initializer_for_16_17, - "Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Mobile/15E148 Safari/604.1" -); - -mod_generator!( - safari17_0, - tls_options!(1, CIPHER_LIST_2), - http2_options!(5), - header_initializer_for_16_17, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15" -); - -mod_generator!( - safari17_2_1, - safari17_0::build_emulation, - header_initializer_for_16_17, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15" -); - -mod_generator!( - safari17_5, - safari17_0::build_emulation, - header_initializer_for_16_17, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15" -); - -mod_generator!( - safari_ios_17_2, - tls_options!(1, CIPHER_LIST_2), - http2_options!(2), - header_initializer_for_16_17, - "Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1" -); - -mod_generator!( - safari_ios_17_4_1, - safari_ios_17_2::build_emulation, - header_initializer_for_16_17, - "Mozilla/5.0 (iPad; CPU OS 17_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Mobile/15E148 Safari/604.1" -); - -mod_generator!( - safari18, - tls_options!(1, CIPHER_LIST_2), - http2_options!(3), - header_initializer_for_18, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15" -); - -mod_generator!( - safari_ipad_18, - safari18::build_emulation, - header_initializer_for_18, - "Mozilla/5.0 (iPad; CPU OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1" -); - -mod_generator!( - safari_ios_18_1_1, - safari18::build_emulation, - header_initializer_for_18, - "Mozilla/5.0 (iPhone; CPU iPhone OS 18_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1.1 Mobile/15E148 Safari/604.1" -); - -mod_generator!( - safari18_2, - tls_options!(2, CIPHER_LIST_2, NEW_SIGALGS_LIST), - http2_options!(3), - header_initializer_for_18, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.2 Safari/605.1.15" -); - -mod_generator!( - safari18_3, - safari18_2::build_emulation, - header_initializer_for_18, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3 Safari/605.1.15" -); - -mod_generator!( - safari18_3_1, - safari18_2::build_emulation, - header_initializer_for_18, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3.1 Safari/605.1.15" -); - -mod_generator!( - safari18_5, - tls_options!(2, CIPHER_LIST_2, NEW_SIGALGS_LIST), - http2_options!(6), - header_initializer_for_18, - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Safari/605.1.15" -); diff --git a/src/emulation/device/safari/header.rs b/src/emulation/device/safari/header.rs new file mode 100644 index 0000000..efa8016 --- /dev/null +++ b/src/emulation/device/safari/header.rs @@ -0,0 +1,59 @@ +use super::*; + +#[inline] +pub fn header_initializer_for_15(ua: &'static str) -> HeaderMap { + let mut headers = HeaderMap::new(); + headers.insert(USER_AGENT, HeaderValue::from_static(ua)); + headers.insert( + ACCEPT, + HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"), + ); + headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.9")); + #[cfg(all(feature = "gzip", feature = "deflate", feature = "brotli"))] + headers.insert( + ACCEPT_ENCODING, + HeaderValue::from_static("gzip, deflate, br"), + ); + headers +} + +#[inline] +pub fn header_initializer_for_16_17(ua: &'static str) -> HeaderMap { + let mut headers = HeaderMap::new(); + headers.insert( + ACCEPT, + HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"), + ); + headers.insert("sec-fetch-site", HeaderValue::from_static("none")); + #[cfg(all(feature = "gzip", feature = "deflate", feature = "brotli"))] + headers.insert( + ACCEPT_ENCODING, + HeaderValue::from_static("gzip, deflate, br"), + ); + headers.insert("sec-fetch-mode", HeaderValue::from_static("navigate")); + headers.insert(USER_AGENT, HeaderValue::from_static(ua)); + headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.9")); + headers.insert("sec-fetch-dest", HeaderValue::from_static("document")); + headers +} + +#[inline] +pub fn header_initializer_for_18(ua: &'static str) -> HeaderMap { + let mut headers = HeaderMap::new(); + headers.insert("sec-fetch-dest", HeaderValue::from_static("document")); + headers.insert(USER_AGENT, HeaderValue::from_static(ua)); + headers.insert( + ACCEPT, + HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"), + ); + headers.insert("sec-fetch-site", HeaderValue::from_static("none")); + headers.insert("sec-fetch-mode", HeaderValue::from_static("navigate")); + headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.9")); + headers.insert("priority", HeaderValue::from_static("u=0, i")); + #[cfg(all(feature = "gzip", feature = "deflate", feature = "brotli"))] + headers.insert( + ACCEPT_ENCODING, + HeaderValue::from_static("gzip, deflate, br"), + ); + headers +} diff --git a/src/emulation/device/safari/macros.rs b/src/emulation/device/safari/macros.rs new file mode 100644 index 0000000..26d07b2 --- /dev/null +++ b/src/emulation/device/safari/macros.rs @@ -0,0 +1,201 @@ +macro_rules! headers_stream_dependency { + (1) => { + StreamDependency::new(StreamId::zero(), 255, true) + }; + (2) => { + StreamDependency::new(StreamId::zero(), 255, false) + }; +} + +macro_rules! headers_pseudo_order { + (1) => { + PseudoOrder::builder() + .extend([ + PseudoId::Method, + PseudoId::Scheme, + PseudoId::Path, + PseudoId::Authority, + ]) + .build() + }; + (2) => { + PseudoOrder::builder() + .extend([ + PseudoId::Method, + PseudoId::Scheme, + PseudoId::Authority, + PseudoId::Path, + ]) + .build() + }; +} + +macro_rules! settings_order { + (1) => { + SettingsOrder::builder() + .extend([ + SettingId::HeaderTableSize, + SettingId::EnablePush, + SettingId::InitialWindowSize, + SettingId::MaxConcurrentStreams, + SettingId::MaxFrameSize, + SettingId::MaxHeaderListSize, + SettingId::EnableConnectProtocol, + SettingId::NoRfc7540Priorities, + ]) + .build() + }; + (2) => { + SettingsOrder::builder() + .extend([ + SettingId::HeaderTableSize, + SettingId::EnablePush, + SettingId::MaxConcurrentStreams, + SettingId::InitialWindowSize, + SettingId::MaxFrameSize, + SettingId::MaxHeaderListSize, + SettingId::EnableConnectProtocol, + SettingId::NoRfc7540Priorities, + ]) + .build() + }; +} + +macro_rules! tls_options { + (1, $cipher_list:expr) => { + SafariTlsConfig::builder() + .cipher_list($cipher_list) + .build() + .into() + }; + (2, $cipher_list:expr, $sigalgs_list:expr) => { + SafariTlsConfig::builder() + .cipher_list($cipher_list) + .sigalgs_list($sigalgs_list) + .build() + .into() + }; +} + +macro_rules! http2_options { + (@base $builder:expr) => { + $builder + .max_concurrent_streams(100) + }; + + (1) => { + http2_options!(@base Http2Options::builder()) + .initial_window_size(2097152) + .initial_connection_window_size(10551295) + .headers_stream_dependency(headers_stream_dependency!(1)) + .headers_pseudo_order(headers_pseudo_order!(1)) + .settings_order(settings_order!(1)) + .build() + }; + (2) => { + http2_options!(@base Http2Options::builder()) + .initial_window_size(2097152) + .initial_connection_window_size(10551295) + .enable_push(false) + .headers_stream_dependency(headers_stream_dependency!(1)) + .headers_pseudo_order(headers_pseudo_order!(1)) + .settings_order(settings_order!(1)) + .build() + }; + (3) => { + http2_options!(@base Http2Options::builder()) + .initial_window_size(2097152) + .initial_connection_window_size(10485760) + .enable_push(false) + .enable_connect_protocol(true) + .no_rfc7540_priorities(true) + .headers_stream_dependency(headers_stream_dependency!(2)) + .headers_pseudo_order(headers_pseudo_order!(2)) + .settings_order(settings_order!(2)) + .build() + }; + (4) => { + http2_options!(@base Http2Options::builder()) + .initial_window_size(4194304) + .initial_connection_window_size(10551295) + .headers_stream_dependency(headers_stream_dependency!(1)) + .headers_pseudo_order(headers_pseudo_order!(1)) + .settings_order(settings_order!(1)) + .build() + }; + (5) => { + http2_options!(@base Http2Options::builder()) + .initial_window_size(4194304) + .initial_connection_window_size(10551295) + .enable_push(false) + .headers_stream_dependency(headers_stream_dependency!(1)) + .headers_pseudo_order(headers_pseudo_order!(1)) + .settings_order(settings_order!(1)) + .build() + }; + (6) => { + http2_options!(@base Http2Options::builder()) + .initial_window_size(2097152) + .initial_connection_window_size(10485760) + .enable_push(false) + .no_rfc7540_priorities(true) + .headers_stream_dependency(headers_stream_dependency!(2)) + .headers_pseudo_order(headers_pseudo_order!(2)) + .settings_order(settings_order!(2)) + .build() + }; +} + +macro_rules! mod_generator { + ($mod_name:ident, $tls_options:expr, $http2_options:expr, $header_initializer:ident, $ua:expr) => { + pub(crate) mod $mod_name { + use super::*; + + #[inline(always)] + pub fn emulation(option: EmulationOption) -> Emulation { + let default_headers = if !option.skip_headers { + Some($header_initializer($ua)) + } else { + None + }; + + build_emulation(option, default_headers) + } + + #[inline(always)] + pub fn build_emulation( + option: EmulationOption, + default_headers: Option, + ) -> Emulation { + let mut builder = Emulation::builder().tls_options($tls_options); + + if !option.skip_http2 { + builder = builder.http2_options($http2_options); + } + + if let Some(headers) = default_headers { + builder = builder.headers(headers); + } + + builder.build() + } + } + }; + + ($mod_name:ident, $build_emulation:expr, $header_initializer:ident, $ua:expr) => { + pub(crate) mod $mod_name { + use super::*; + + #[inline(always)] + pub fn emulation(option: EmulationOption) -> Emulation { + let default_headers = if !option.skip_headers { + Some($header_initializer($ua)) + } else { + None + }; + + $build_emulation(option, default_headers) + } + } + }; +} diff --git a/src/emulation/device/safari/mod.rs b/src/emulation/device/safari/mod.rs new file mode 100644 index 0000000..488639a --- /dev/null +++ b/src/emulation/device/safari/mod.rs @@ -0,0 +1,150 @@ +#[macro_use] +mod macros; +mod header; +mod tls; + +use header::*; +use tls::*; + +use super::{emulation_imports::*, http2_imports::*, *}; + +mod_generator!( + safari15_3, + tls_options!(1, CIPHER_LIST_1), + http2_options!(4), + header_initializer_for_15, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.3 Safari/605.1.15" +); + +mod_generator!( + safari15_5, + safari15_3::build_emulation, + header_initializer_for_15, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Safari/605.1.15" +); + +mod_generator!( + safari15_6_1, + tls_options!(1, CIPHER_LIST_2), + http2_options!(4), + header_initializer_for_15, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6.1 Safari/605.1.15" +); + +mod_generator!( + safari16, + safari15_6_1::build_emulation, + header_initializer_for_16_17, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15" +); + +mod_generator!( + safari16_5, + safari15_6_1::build_emulation, + header_initializer_for_16_17, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Safari/605.1.15" +); + +mod_generator!( + safari17_4_1, + safari15_6_1::build_emulation, + header_initializer_for_16_17, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Safari/605.1.15" +); + +mod_generator!( + safari_ios_16_5, + tls_options!(1, CIPHER_LIST_2), + http2_options!(1), + header_initializer_for_16_17, + "Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Mobile/15E148 Safari/604.1" +); + +mod_generator!( + safari17_0, + tls_options!(1, CIPHER_LIST_2), + http2_options!(5), + header_initializer_for_16_17, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15" +); + +mod_generator!( + safari17_2_1, + safari17_0::build_emulation, + header_initializer_for_16_17, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15" +); + +mod_generator!( + safari17_5, + safari17_0::build_emulation, + header_initializer_for_16_17, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15" +); + +mod_generator!( + safari_ios_17_2, + tls_options!(1, CIPHER_LIST_2), + http2_options!(2), + header_initializer_for_16_17, + "Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1" +); + +mod_generator!( + safari_ios_17_4_1, + safari_ios_17_2::build_emulation, + header_initializer_for_16_17, + "Mozilla/5.0 (iPad; CPU OS 17_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Mobile/15E148 Safari/604.1" +); + +mod_generator!( + safari18, + tls_options!(1, CIPHER_LIST_2), + http2_options!(3), + header_initializer_for_18, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15" +); + +mod_generator!( + safari_ipad_18, + safari18::build_emulation, + header_initializer_for_18, + "Mozilla/5.0 (iPad; CPU OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1" +); + +mod_generator!( + safari_ios_18_1_1, + safari18::build_emulation, + header_initializer_for_18, + "Mozilla/5.0 (iPhone; CPU iPhone OS 18_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1.1 Mobile/15E148 Safari/604.1" +); + +mod_generator!( + safari18_2, + tls_options!(2, CIPHER_LIST_2, NEW_SIGALGS_LIST), + http2_options!(3), + header_initializer_for_18, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.2 Safari/605.1.15" +); + +mod_generator!( + safari18_3, + safari18_2::build_emulation, + header_initializer_for_18, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3 Safari/605.1.15" +); + +mod_generator!( + safari18_3_1, + safari18_2::build_emulation, + header_initializer_for_18, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3.1 Safari/605.1.15" +); + +mod_generator!( + safari18_5, + tls_options!(2, CIPHER_LIST_2, NEW_SIGALGS_LIST), + http2_options!(6), + header_initializer_for_18, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Safari/605.1.15" +); diff --git a/src/emulation/device/safari/tls.rs b/src/emulation/device/safari/tls.rs new file mode 100644 index 0000000..d92a0ac --- /dev/null +++ b/src/emulation/device/safari/tls.rs @@ -0,0 +1,115 @@ +use super::tls_imports::*; + +pub const CURVES: &str = join!(":", "X25519", "P-256", "P-384", "P-521"); + +pub const CIPHER_LIST_1: &str = join!( + ":", + "TLS_AES_128_GCM_SHA256", + "TLS_AES_256_GCM_SHA384", + "TLS_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_CBC_SHA256", + "TLS_RSA_WITH_AES_128_CBC_SHA256", + "TLS_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", + "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + "TLS_RSA_WITH_3DES_EDE_CBC_SHA" +); +pub const CIPHER_LIST_2: &str = join!( + ":", + "TLS_AES_128_GCM_SHA256", + "TLS_AES_256_GCM_SHA384", + "TLS_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", + "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + "TLS_RSA_WITH_3DES_EDE_CBC_SHA" +); + +pub const SIGALGS_LIST: &str = join!( + ":", + "ecdsa_secp256r1_sha256", + "rsa_pss_rsae_sha256", + "rsa_pkcs1_sha256", + "ecdsa_secp384r1_sha384", + "ecdsa_sha1", + "rsa_pss_rsae_sha384", + "rsa_pss_rsae_sha384", + "rsa_pkcs1_sha384", + "rsa_pss_rsae_sha512", + "rsa_pkcs1_sha512", + "rsa_pkcs1_sha1" +); + +pub const NEW_SIGALGS_LIST: &str = join!( + ":", + "ecdsa_secp256r1_sha256", + "rsa_pss_rsae_sha256", + "rsa_pkcs1_sha256", + "ecdsa_secp384r1_sha384", + "rsa_pss_rsae_sha384", + "rsa_pss_rsae_sha384", + "rsa_pkcs1_sha384", + "rsa_pss_rsae_sha512", + "rsa_pkcs1_sha512", + "rsa_pkcs1_sha1" +); + +pub const CERT_COMPRESSION_ALGORITHM: &[CertificateCompressionAlgorithm] = + &[CertificateCompressionAlgorithm::ZLIB]; + +#[derive(TypedBuilder)] +pub struct SafariTlsConfig { + #[builder(default = CURVES)] + curves: &'static str, + + #[builder(default = SIGALGS_LIST)] + sigalgs_list: &'static str, + + cipher_list: &'static str, +} + +impl From for TlsOptions { + fn from(val: SafariTlsConfig) -> Self { + TlsOptions::builder() + .session_ticket(false) + .grease_enabled(true) + .enable_ocsp_stapling(true) + .enable_signed_cert_timestamps(true) + .curves_list(val.curves) + .sigalgs_list(val.sigalgs_list) + .cipher_list(val.cipher_list) + .min_tls_version(TlsVersion::TLS_1_0) + .certificate_compression_algorithms(CERT_COMPRESSION_ALGORITHM) + .build() + } +}