Skip to main content

bimp_persona/
options.rs

1use serde::{Deserialize, Serialize};
2
3use crate::fingerprint::*;
4use crate::seed::PersonaSeed;
5
6/// CSS viewport dimensions and device scale factor.
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8pub struct Viewport {
9    pub width: u32,
10    pub height: u32,
11    pub device_scale_factor: u32,
12}
13
14impl Default for Viewport {
15    fn default() -> Self {
16        Self {
17            width: 1440,
18            height: 900,
19            device_scale_factor: 1,
20        }
21    }
22}
23
24/// Optional screen fingerprint overrides.
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
26pub struct ScreenConfig {
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub width: Option<u32>,
29    #[serde(default, skip_serializing_if = "Option::is_none")]
30    pub height: Option<u32>,
31    #[serde(default, skip_serializing_if = "Option::is_none")]
32    pub avail_width: Option<u32>,
33    #[serde(default, skip_serializing_if = "Option::is_none")]
34    pub avail_height: Option<u32>,
35    #[serde(default, skip_serializing_if = "Option::is_none")]
36    pub avail_left: Option<i32>,
37    #[serde(default, skip_serializing_if = "Option::is_none")]
38    pub avail_top: Option<i32>,
39    #[serde(default, skip_serializing_if = "Option::is_none")]
40    pub left: Option<i32>,
41    #[serde(default, skip_serializing_if = "Option::is_none")]
42    pub top: Option<i32>,
43    #[serde(default, skip_serializing_if = "Option::is_none")]
44    pub color_depth: Option<u32>,
45    #[serde(default, skip_serializing_if = "Option::is_none")]
46    pub pixel_depth: Option<u32>,
47    #[serde(default, skip_serializing_if = "Option::is_none")]
48    pub is_extended: Option<bool>,
49    #[serde(default, skip_serializing_if = "Option::is_none")]
50    pub orientation_type: Option<String>,
51    #[serde(default, skip_serializing_if = "Option::is_none")]
52    pub orientation_angle: Option<u16>,
53}
54
55/// Optional window fingerprint overrides.
56#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
57pub struct WindowConfig {
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub outer_width: Option<u32>,
60    #[serde(default, skip_serializing_if = "Option::is_none")]
61    pub outer_height: Option<u32>,
62    #[serde(default, skip_serializing_if = "Option::is_none")]
63    pub screen_x: Option<i32>,
64    #[serde(default, skip_serializing_if = "Option::is_none")]
65    pub screen_y: Option<i32>,
66}
67
68/// Optional CSS feature-detection overrides.
69#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
70pub struct CssConfig {
71    #[serde(default, skip_serializing_if = "Option::is_none")]
72    pub moz_prefix_enabled: Option<bool>,
73    #[serde(default, skip_serializing_if = "Option::is_none")]
74    pub webkit_prefix_enabled: Option<bool>,
75}
76
77/// Optional WebGL and graphics fingerprint overrides.
78#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
79pub struct GraphicsConfig {
80    #[serde(default, skip_serializing_if = "Option::is_none")]
81    pub webgl_vendor: Option<String>,
82    #[serde(default, skip_serializing_if = "Option::is_none")]
83    pub webgl_renderer: Option<String>,
84    #[serde(default, skip_serializing_if = "Option::is_none")]
85    pub webgl_masked_vendor: Option<String>,
86    #[serde(default, skip_serializing_if = "Option::is_none")]
87    pub webgl_masked_renderer: Option<String>,
88}
89
90/// Optional `navigator` and JS capability overrides.
91#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
92pub struct NavigatorConfig {
93    #[serde(default, skip_serializing_if = "Option::is_none")]
94    pub app_version: Option<String>,
95    #[serde(default, skip_serializing_if = "Option::is_none")]
96    pub platform: Option<String>,
97    #[serde(default, skip_serializing_if = "Option::is_none")]
98    pub language: Option<String>,
99    #[serde(default, skip_serializing_if = "Option::is_none")]
100    pub languages: Option<Vec<String>>,
101    #[serde(default, skip_serializing_if = "Option::is_none")]
102    pub hardware_concurrency: Option<u32>,
103    #[serde(default, skip_serializing_if = "Option::is_none")]
104    pub device_memory_gb: Option<u32>,
105    #[serde(default, skip_serializing_if = "Option::is_none")]
106    pub max_touch_points: Option<u32>,
107    #[serde(default, skip_serializing_if = "Option::is_none")]
108    pub do_not_track: Option<String>,
109    #[serde(default, skip_serializing_if = "Option::is_none")]
110    pub global_privacy_control: Option<bool>,
111    #[serde(default, skip_serializing_if = "Option::is_none")]
112    pub expose_global_privacy_control: Option<bool>,
113    #[serde(default, skip_serializing_if = "Option::is_none")]
114    pub permissions_enabled: Option<bool>,
115    #[serde(default, skip_serializing_if = "Option::is_none")]
116    pub bluetooth_enabled: Option<bool>,
117    #[serde(default, skip_serializing_if = "Option::is_none")]
118    pub bluetooth_available: Option<bool>,
119    #[serde(default, skip_serializing_if = "Option::is_none")]
120    pub media_devices_enabled: Option<bool>,
121    #[serde(default, skip_serializing_if = "Option::is_none")]
122    pub webgpu_enabled: Option<bool>,
123    #[serde(default, skip_serializing_if = "Option::is_none")]
124    pub offscreen_canvas_enabled: Option<bool>,
125    #[serde(default, skip_serializing_if = "Option::is_none")]
126    pub service_worker_enabled: Option<bool>,
127    #[serde(default, skip_serializing_if = "Option::is_none")]
128    pub ua_platform_version: Option<String>,
129    #[serde(default, skip_serializing_if = "Option::is_none")]
130    pub ua_architecture: Option<String>,
131    #[serde(default, skip_serializing_if = "Option::is_none")]
132    pub ua_bitness: Option<String>,
133    #[serde(default, skip_serializing_if = "Option::is_none")]
134    pub ua_model: Option<String>,
135    #[serde(default, skip_serializing_if = "Option::is_none")]
136    pub vendor: Option<String>,
137    #[serde(default, skip_serializing_if = "Option::is_none")]
138    pub product_sub: Option<String>,
139    #[serde(default, skip_serializing_if = "Option::is_none")]
140    pub pdf_viewer_enabled: Option<bool>,
141}
142
143/// Optional media device and speech synthesis overrides.
144#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
145pub struct MediaConfig {
146    #[serde(default, skip_serializing_if = "Option::is_none")]
147    pub speech_voices: Option<Vec<String>>,
148    #[serde(default, skip_serializing_if = "Option::is_none")]
149    pub audio_inputs: Option<u32>,
150    #[serde(default, skip_serializing_if = "Option::is_none")]
151    pub video_inputs: Option<u32>,
152    #[serde(default, skip_serializing_if = "Option::is_none")]
153    pub audio_outputs: Option<u32>,
154}
155
156/// Optional font list and font seed overrides.
157#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
158pub struct FontConfig {
159    #[serde(default, skip_serializing_if = "Option::is_none")]
160    pub families: Option<Vec<String>>,
161    #[serde(default, skip_serializing_if = "Option::is_none")]
162    pub seed: Option<PersonaSeed>,
163}
164
165/// Optional canvas fingerprint behavior overrides.
166#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
167pub struct CanvasConfig {
168    #[serde(default, skip_serializing_if = "Option::is_none")]
169    pub noise_enabled: Option<bool>,
170    #[serde(default, skip_serializing_if = "Option::is_none")]
171    pub noise_amplitude: Option<u8>,
172    #[serde(default, skip_serializing_if = "Option::is_none")]
173    pub blink_low_entropy_probe: Option<bool>,
174    #[serde(default, skip_serializing_if = "Option::is_none")]
175    pub seed: Option<PersonaSeed>,
176}
177
178/// Optional DOMRect/SVGRect fingerprint behavior overrides.
179#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
180pub struct DomRectConfig {
181    #[serde(default, skip_serializing_if = "Option::is_none")]
182    pub enabled: Option<bool>,
183    #[serde(default, skip_serializing_if = "Option::is_none")]
184    pub quantization_steps_per_px: Option<u32>,
185    #[serde(default, skip_serializing_if = "Option::is_none")]
186    pub fill_empty_client_rects: Option<bool>,
187    #[serde(default, skip_serializing_if = "Option::is_none")]
188    pub seed: Option<PersonaSeed>,
189}
190
191/// Optional JavaScript engine fingerprint overrides.
192#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
193pub struct EngineConfig {
194    #[serde(default, skip_serializing_if = "Option::is_none")]
195    pub enabled: Option<bool>,
196    #[serde(default, skip_serializing_if = "Option::is_none")]
197    pub to_fixed_range_error_message: Option<String>,
198    #[serde(default, skip_serializing_if = "Option::is_none")]
199    pub array_constructor_source: Option<String>,
200}
201
202impl EngineConfig {
203    pub(crate) fn apply_to(&self, engine: &mut EngineFingerprint) {
204        if let Some(enabled) = self.enabled {
205            engine.enabled = enabled;
206        }
207        if let Some(message) = self
208            .to_fixed_range_error_message
209            .as_ref()
210            .filter(|value| !value.is_empty())
211        {
212            engine.to_fixed_range_error_message = message.clone();
213        }
214        if let Some(source) = self
215            .array_constructor_source
216            .as_ref()
217            .filter(|value| !value.is_empty())
218        {
219            engine.array_constructor_source = source.clone();
220        }
221    }
222}
223
224impl DomRectConfig {
225    pub(crate) fn apply_to(&self, domrect: &mut DomRectFingerprint) {
226        if let Some(enabled) = self.enabled {
227            domrect.enabled = enabled;
228        }
229        if let Some(quantization_steps_per_px) = self.quantization_steps_per_px {
230            domrect.quantization_steps_per_px = quantization_steps_per_px;
231        }
232        if let Some(fill_empty_client_rects) = self.fill_empty_client_rects {
233            domrect.fill_empty_client_rects = fill_empty_client_rects;
234        }
235        if let Some(seed) = self.seed.as_ref() {
236            domrect.seed = seed.clone();
237        }
238    }
239}
240
241impl CanvasConfig {
242    pub(crate) fn apply_to(&self, canvas: &mut CanvasFingerprint) {
243        if let Some(noise_enabled) = self.noise_enabled {
244            canvas.noise_enabled = noise_enabled;
245        }
246        if let Some(noise_amplitude) = self.noise_amplitude {
247            canvas.noise_amplitude = noise_amplitude;
248        }
249        if let Some(blink_low_entropy_probe) = self.blink_low_entropy_probe {
250            canvas.blink_low_entropy_probe = blink_low_entropy_probe;
251        }
252        if let Some(seed) = self.seed.as_ref() {
253            canvas.seed = seed.clone();
254        }
255    }
256}
257
258impl MediaConfig {
259    pub(crate) fn apply_to(&self, media: &mut MediaFingerprint) {
260        if let Some(speech_voices) = self
261            .speech_voices
262            .as_ref()
263            .filter(|voices| !voices.is_empty())
264        {
265            media.speech_voices = speech_voices.clone();
266        }
267        if let Some(audio_inputs) = self.audio_inputs {
268            media.audio_inputs = audio_inputs;
269        }
270        if let Some(video_inputs) = self.video_inputs {
271            media.video_inputs = video_inputs;
272        }
273        if let Some(audio_outputs) = self.audio_outputs {
274            media.audio_outputs = audio_outputs;
275        }
276    }
277}
278
279impl FontConfig {
280    pub(crate) fn apply_to(&self, fonts: &mut FontFingerprint) {
281        if let Some(families) = self
282            .families
283            .as_ref()
284            .map(|families| {
285                families
286                    .iter()
287                    .map(|family| family.trim())
288                    .filter(|family| !family.is_empty())
289                    .map(ToOwned::to_owned)
290                    .collect::<Vec<_>>()
291            })
292            .filter(|families| !families.is_empty())
293        {
294            fonts.families = families;
295        }
296        if let Some(seed) = self.seed.as_ref() {
297            fonts.seed = seed.clone();
298        }
299    }
300}
301
302impl NavigatorConfig {
303    pub(crate) fn apply_to(&self, js: &mut JsFingerprint) {
304        if let Some(app_version) = self.app_version.as_ref().filter(|value| !value.is_empty()) {
305            js.app_version = app_version.clone();
306        }
307        if let Some(platform) = self.platform.as_ref().filter(|value| !value.is_empty()) {
308            js.platform = platform.clone();
309        }
310        if let Some(language) = self.language.as_ref().filter(|value| !value.is_empty()) {
311            js.language = language.clone();
312        }
313        if let Some(languages) = self.languages.as_ref().filter(|values| !values.is_empty()) {
314            js.languages = languages.clone();
315            if self.language.is_none()
316                && let Some(language) = js.languages.first()
317            {
318                js.language = language.clone();
319            }
320        }
321        if let Some(hardware_concurrency) = self.hardware_concurrency {
322            js.hardware_concurrency = hardware_concurrency;
323        }
324        if let Some(device_memory_gb) = self.device_memory_gb {
325            js.device_memory_gb = device_memory_gb;
326        }
327        if let Some(max_touch_points) = self.max_touch_points {
328            js.max_touch_points = max_touch_points;
329        }
330        if let Some(do_not_track) = self.do_not_track.as_ref() {
331            js.do_not_track = do_not_track.clone();
332        }
333        if let Some(global_privacy_control) = self.global_privacy_control {
334            js.global_privacy_control = global_privacy_control;
335        }
336        if let Some(expose_global_privacy_control) = self.expose_global_privacy_control {
337            js.expose_global_privacy_control = expose_global_privacy_control;
338        }
339        if let Some(permissions_enabled) = self.permissions_enabled {
340            js.permissions_enabled = permissions_enabled;
341        }
342        if let Some(bluetooth_enabled) = self.bluetooth_enabled {
343            js.bluetooth_enabled = bluetooth_enabled;
344        }
345        if let Some(bluetooth_available) = self.bluetooth_available {
346            js.bluetooth_available = bluetooth_available;
347        }
348        if let Some(media_devices_enabled) = self.media_devices_enabled {
349            js.media_devices_enabled = media_devices_enabled;
350        }
351        if let Some(webgpu_enabled) = self.webgpu_enabled {
352            js.webgpu_enabled = webgpu_enabled;
353        }
354        if let Some(offscreen_canvas_enabled) = self.offscreen_canvas_enabled {
355            js.offscreen_canvas_enabled = offscreen_canvas_enabled;
356        }
357        if let Some(service_worker_enabled) = self.service_worker_enabled {
358            js.service_worker_enabled = service_worker_enabled;
359        }
360        if let Some(ua_platform_version) = self
361            .ua_platform_version
362            .as_ref()
363            .filter(|value| !value.is_empty())
364        {
365            js.ua_platform_version = ua_platform_version.clone();
366        }
367        if let Some(ua_architecture) = self
368            .ua_architecture
369            .as_ref()
370            .filter(|value| !value.is_empty())
371        {
372            js.ua_architecture = ua_architecture.clone();
373        }
374        if let Some(ua_bitness) = self.ua_bitness.as_ref().filter(|value| !value.is_empty()) {
375            js.ua_bitness = ua_bitness.clone();
376        }
377        if let Some(ua_model) = self.ua_model.as_ref() {
378            js.ua_model = ua_model.clone();
379        }
380        if let Some(vendor) = self.vendor.as_ref().filter(|value| !value.is_empty()) {
381            js.vendor = vendor.clone();
382        }
383        if let Some(product_sub) = self.product_sub.as_ref().filter(|value| !value.is_empty()) {
384            js.product_sub = product_sub.clone();
385        }
386        if let Some(pdf_viewer_enabled) = self.pdf_viewer_enabled {
387            js.pdf_viewer_enabled = pdf_viewer_enabled;
388        }
389    }
390}
391
392impl GraphicsConfig {
393    pub(crate) fn apply_to(&self, graphics: &mut GraphicsFingerprint) {
394        if let Some(webgl_vendor) = self.webgl_vendor.as_ref().filter(|value| !value.is_empty()) {
395            graphics.webgl_vendor = webgl_vendor.clone();
396        }
397        if let Some(webgl_renderer) = self
398            .webgl_renderer
399            .as_ref()
400            .filter(|value| !value.is_empty())
401        {
402            graphics.webgl_renderer = webgl_renderer.clone();
403        }
404        if let Some(webgl_masked_vendor) = self
405            .webgl_masked_vendor
406            .as_ref()
407            .filter(|value| !value.is_empty())
408        {
409            graphics.webgl_masked_vendor = webgl_masked_vendor.clone();
410        }
411        if let Some(webgl_masked_renderer) = self
412            .webgl_masked_renderer
413            .as_ref()
414            .filter(|value| !value.is_empty())
415        {
416            graphics.webgl_masked_renderer = webgl_masked_renderer.clone();
417        }
418    }
419}
420
421impl ScreenConfig {
422    pub(crate) fn apply_to(&self, screen: &mut ScreenFingerprint) {
423        if let Some(width) = self.width {
424            screen.width = width;
425        }
426        if let Some(height) = self.height {
427            screen.height = height;
428        }
429        if let Some(avail_width) = self.avail_width {
430            screen.avail_width = avail_width;
431        }
432        if let Some(avail_height) = self.avail_height {
433            screen.avail_height = avail_height;
434        }
435        if let Some(avail_left) = self.avail_left {
436            screen.avail_left = avail_left;
437        }
438        if let Some(avail_top) = self.avail_top {
439            screen.avail_top = avail_top;
440        }
441        if let Some(left) = self.left {
442            screen.left = left;
443        }
444        if let Some(top) = self.top {
445            screen.top = top;
446        }
447        if let Some(color_depth) = self.color_depth {
448            screen.color_depth = color_depth;
449        }
450        if let Some(pixel_depth) = self.pixel_depth {
451            screen.pixel_depth = pixel_depth;
452        }
453        if let Some(is_extended) = self.is_extended {
454            screen.is_extended = is_extended;
455        }
456        if let Some(orientation_type) = self
457            .orientation_type
458            .as_ref()
459            .filter(|value| !value.is_empty())
460        {
461            screen.orientation_type = orientation_type.clone();
462        }
463        if let Some(orientation_angle) = self.orientation_angle {
464            screen.orientation_angle = orientation_angle;
465        }
466    }
467}
468
469impl WindowConfig {
470    pub(crate) fn apply_to(&self, window: &mut WindowFingerprint) {
471        if let Some(outer_width) = self.outer_width {
472            window.outer_width = outer_width;
473        }
474        if let Some(outer_height) = self.outer_height {
475            window.outer_height = outer_height;
476        }
477        if let Some(screen_x) = self.screen_x {
478            window.screen_x = screen_x;
479        }
480        if let Some(screen_y) = self.screen_y {
481            window.screen_y = screen_y;
482        }
483    }
484}
485
486impl CssConfig {
487    pub(crate) fn apply_to(&self, css: &mut CssFingerprint) {
488        if let Some(moz_prefix_enabled) = self.moz_prefix_enabled {
489            css.moz_prefix_enabled = moz_prefix_enabled;
490        }
491        if let Some(webkit_prefix_enabled) = self.webkit_prefix_enabled {
492            css.webkit_prefix_enabled = webkit_prefix_enabled;
493        }
494    }
495}