rama_ua/profile/
http.rs

1use rama_http_types::{
2    HeaderName, Method, Version,
3    conn::StreamDependencyParams,
4    proto::{
5        h1::Http1HeaderMap,
6        h2::{PseudoHeaderOrder, frame::SettingsConfig},
7    },
8};
9use rama_net::fingerprint::{HttpRequestInput, Ja4H, Ja4HComputeError};
10use serde::{Deserialize, Serialize};
11use std::sync::Arc;
12
13/// Marker header name for custom headers.
14///
15/// Header value: `x-rama-custom-header-marker`
16///
17/// This is used to identify in the [`HttpHeadersProfile`]
18/// the initial location of custom headers, which is also
19/// by the [`UserAgentEmulateRequestModifier`] used to place
20/// any original request headers that were not present in the
21/// [`HttpHeadersProfile`] (also called base headers).
22///
23/// If this header is not present in the [`HttpHeadersProfile`]
24/// then it will be assumed that remaining headers are to be
25/// put as the final headers in the request header map.
26///
27/// [`HttpHeadersProfile`]: crate::profile::HttpHeadersProfile
28/// [`UserAgentEmulateHttpRequestModifier`]: crate::emulate::UserAgentEmulateHttpRequestModifier
29pub static CUSTOM_HEADER_MARKER: HeaderName =
30    HeaderName::from_static("x-rama-custom-header-marker");
31
32#[derive(Debug, Clone)]
33/// A User Agent (UA) profile for HTTP.
34///
35/// This profile contains the HTTP profiles for
36/// [`Http1`][`Http1Profile`] and [`Http2`][`Http2Profile`].
37///
38/// [`Http1Profile`]: crate::profile::Http1Profile
39/// [`Http2Profile`]: crate::profile::Http2Profile
40pub struct HttpProfile {
41    /// The HTTP/1.1 profile.
42    pub h1: Arc<Http1Profile>,
43    /// The HTTP/2 profile.
44    pub h2: Arc<Http2Profile>,
45}
46
47impl HttpProfile {
48    /// Compute the [`Ja4H`] (hash) for the h1 navigate headers in this [`HttpProfile`].
49    ///
50    /// As specified by <https://blog.foxio.io/ja4%2B-network-fingerprinting>
51    /// and reference implementations found at <https://github.com/FoxIO-LLC/ja4>.
52    pub fn ja4h_h1_navigate(&self, method: Option<Method>) -> Result<Ja4H, Ja4HComputeError> {
53        Ja4H::compute(HttpRequestInput {
54            header_map: self.h1.headers.navigate.clone(),
55            http_method: method.unwrap_or(Method::GET),
56            version: Version::HTTP_11,
57        })
58    }
59
60    /// Compute the [`Ja4H`] (hash) for the h1 fetch headers in this [`HttpProfile`], if such headers are available for fetch.
61    ///
62    /// As specified by <https://blog.foxio.io/ja4%2B-network-fingerprinting>
63    /// and reference implementations found at <https://github.com/FoxIO-LLC/ja4>.
64    pub fn ja4h_h1_fetch(&self, method: Option<Method>) -> Option<Result<Ja4H, Ja4HComputeError>> {
65        self.h1.headers.fetch.clone().map(|header_map| {
66            Ja4H::compute(HttpRequestInput {
67                header_map,
68                http_method: method.unwrap_or(Method::GET),
69                version: Version::HTTP_11,
70            })
71        })
72    }
73
74    /// Compute the [`Ja4H`] (hash) for the h1 xhr headers in this [`HttpProfile`], if such headers are available for xhr.
75    ///
76    /// As specified by <https://blog.foxio.io/ja4%2B-network-fingerprinting>
77    /// and reference implementations found at <https://github.com/FoxIO-LLC/ja4>.
78    pub fn ja4h_h1_xhr(&self, method: Option<Method>) -> Option<Result<Ja4H, Ja4HComputeError>> {
79        self.h1.headers.xhr.clone().map(|header_map| {
80            Ja4H::compute(HttpRequestInput {
81                header_map,
82                http_method: method.unwrap_or(Method::GET),
83                version: Version::HTTP_11,
84            })
85        })
86    }
87
88    /// Compute the [`Ja4H`] (hash) for the h1 form headers in this [`HttpProfile`], if such headers are available for form.
89    ///
90    /// As specified by <https://blog.foxio.io/ja4%2B-network-fingerprinting>
91    /// and reference implementations found at <https://github.com/FoxIO-LLC/ja4>.
92    pub fn ja4h_h1_form(&self, method: Option<Method>) -> Option<Result<Ja4H, Ja4HComputeError>> {
93        self.h1.headers.form.clone().map(|header_map| {
94            Ja4H::compute(HttpRequestInput {
95                header_map,
96                http_method: method.unwrap_or(Method::GET),
97                version: Version::HTTP_11,
98            })
99        })
100    }
101
102    /// Compute the [`Ja4H`] (hash) for the h2 navigate headers in this [`HttpProfile`].
103    ///
104    /// As specified by <https://blog.foxio.io/ja4%2B-network-fingerprinting>
105    /// and reference implementations found at <https://github.com/FoxIO-LLC/ja4>.
106    pub fn ja4h_h2_navigate(&self, method: Option<Method>) -> Result<Ja4H, Ja4HComputeError> {
107        Ja4H::compute(HttpRequestInput {
108            header_map: self.h2.headers.navigate.clone(),
109            http_method: method.unwrap_or(Method::GET),
110            version: Version::HTTP_2,
111        })
112    }
113
114    /// Compute the [`Ja4H`] (hash) for the h2 fetch headers in this [`HttpProfile`], if such headers are available for fetch.
115    ///
116    /// As specified by <https://blog.foxio.io/ja4%2B-network-fingerprinting>
117    /// and reference implementations found at <https://github.com/FoxIO-LLC/ja4>.
118    pub fn ja4h_h2_fetch(&self, method: Option<Method>) -> Option<Result<Ja4H, Ja4HComputeError>> {
119        self.h2.headers.fetch.clone().map(|header_map| {
120            Ja4H::compute(HttpRequestInput {
121                header_map,
122                http_method: method.unwrap_or(Method::GET),
123                version: Version::HTTP_2,
124            })
125        })
126    }
127
128    /// Compute the [`Ja4H`] (hash) for the h2 xhr headers in this [`HttpProfile`], if such headers are available for xhr.
129    ///
130    /// As specified by <https://blog.foxio.io/ja4%2B-network-fingerprinting>
131    /// and reference implementations found at <https://github.com/FoxIO-LLC/ja4>.
132    pub fn ja4h_h2_xhr(&self, method: Option<Method>) -> Option<Result<Ja4H, Ja4HComputeError>> {
133        self.h2.headers.xhr.clone().map(|header_map| {
134            Ja4H::compute(HttpRequestInput {
135                header_map,
136                http_method: method.unwrap_or(Method::GET),
137                version: Version::HTTP_2,
138            })
139        })
140    }
141
142    /// Compute the [`Ja4H`] (hash) for the h2 form headers in this [`HttpProfile`], if such headers are available for form.
143    ///
144    /// As specified by <https://blog.foxio.io/ja4%2B-network-fingerprinting>
145    /// and reference implementations found at <https://github.com/FoxIO-LLC/ja4>.
146    pub fn ja4h_h2_form(&self, method: Option<Method>) -> Option<Result<Ja4H, Ja4HComputeError>> {
147        self.h2.headers.form.clone().map(|header_map| {
148            Ja4H::compute(HttpRequestInput {
149                header_map,
150                http_method: method.unwrap_or(Method::GET),
151                version: Version::HTTP_2,
152            })
153        })
154    }
155}
156
157impl<'de> Deserialize<'de> for HttpProfile {
158    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
159    where
160        D: serde::Deserializer<'de>,
161    {
162        let input = HttpProfileDeserialize::deserialize(deserializer)?;
163        Ok(Self {
164            h1: Arc::new(input.h1),
165            h2: Arc::new(input.h2),
166        })
167    }
168}
169
170impl Serialize for HttpProfile {
171    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
172    where
173        S: serde::Serializer,
174    {
175        HttpProfileSerialize {
176            h1: self.h1.as_ref(),
177            h2: self.h2.as_ref(),
178        }
179        .serialize(serializer)
180    }
181}
182
183#[derive(Debug, Serialize)]
184struct HttpProfileSerialize<'a> {
185    h1: &'a Http1Profile,
186    h2: &'a Http2Profile,
187}
188
189#[derive(Debug, Deserialize)]
190struct HttpProfileDeserialize {
191    h1: Http1Profile,
192    h2: Http2Profile,
193}
194
195#[derive(Debug, Deserialize, Serialize)]
196pub struct HttpHeadersProfile {
197    /// The headers to be used for navigation requests.
198    ///
199    /// A navigation request is the regular request that a user-agent
200    /// makes automatically or on behalf of the user, but that is not
201    /// triggered directly by a script.
202    pub navigate: Http1HeaderMap,
203    /// The headers to be used for fetch requests.
204    ///
205    /// A fetch request is a request made by a script to retrieve a resource from a server,
206    /// using the [`fetch`][`fetch`] API.
207    ///
208    /// In case the user-agent does not support the [`fetch`][`fetch`] API,
209    /// then it is recommended to try to use the `xhr` headers,
210    /// and as a final fallback use the `navigate` headers.
211    ///
212    /// [`fetch`]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
213    pub fetch: Option<Http1HeaderMap>,
214    /// The headers to be used for XMLHttpRequest requests.
215    ///
216    /// An [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)
217    /// is a request made by a script to retrieve a resource from a server.
218    ///
219    /// In case the user-agent does not support the [`XMLHttpRequest`][`XMLHttpRequest`] API,
220    /// then it is recommended to try to use the `fetch` headers,
221    /// and as a final fallback use the `navigate` headers.
222    pub xhr: Option<Http1HeaderMap>,
223    /// The headers to be used for form submissions.
224    ///
225    /// A form submission is a request made by a script to submit a form to a server.
226    ///
227    /// In case the user-agent does not support forms (e.g. because it does not handle html forms),
228    /// then it is recommended to try to use the `fetch` headers and any fallbacks that the latter may have.
229    pub form: Option<Http1HeaderMap>,
230}
231
232#[derive(Debug, Deserialize, Serialize)]
233/// The HTTP/1.1 profile.
234///
235/// This profile contains the headers and settings for the HTTP/1.1 protocol.
236pub struct Http1Profile {
237    /// The (base) headers to be used for the HTTP/1.1 profile.
238    pub headers: HttpHeadersProfile,
239    /// The settings for the HTTP/1.1 profile.
240    pub settings: Http1Settings,
241}
242
243#[derive(Debug, Deserialize, Serialize, Default)]
244/// The settings for the HTTP/1.1 profile.
245pub struct Http1Settings {
246    /// Whether to enforce title case the headers.
247    pub title_case_headers: bool,
248}
249
250#[derive(Debug, Deserialize, Serialize)]
251/// The HTTP/2 profile.
252///
253/// This profile contains the headers and settings for the HTTP/2 protocol.
254pub struct Http2Profile {
255    /// The headers to be used for the HTTP/2 profile.
256    pub headers: HttpHeadersProfile,
257    /// The settings for the HTTP/2 profile.
258    pub settings: Http2Settings,
259}
260
261#[derive(Debug, Clone, Deserialize, Serialize, Default)]
262/// The settings for the HTTP/2 profile.
263pub struct Http2Settings {
264    /// The pseudo headers to be used for the HTTP/2 profile.
265    ///
266    /// See [`PseudoHeader`] for more details.
267    pub http_pseudo_headers: Option<PseudoHeaderOrder>,
268
269    /// The (initial) h2 settings to be used for the HTTP/2 profile.
270    ///
271    /// See [`SettingsConfig`] for more details.
272    pub initial_config: Option<SettingsConfig>,
273
274    /// The priority settings to be used for the HTTP/2 profile.
275    ///
276    /// See [`StreamDependencyParams`] for more details.
277    pub priority_header: Option<StreamDependencyParams>,
278}