#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FieldMechanism {
Browser,
Script,
}
pub const FINGERPRINT_FIELD_MECHANISMS: &[(&str, FieldMechanism)] = &[
("userAgent", FieldMechanism::Browser),
("userAgentData", FieldMechanism::Browser),
("acceptLanguage", FieldMechanism::Browser),
("languages", FieldMechanism::Browser),
("locale", FieldMechanism::Browser),
("timezoneId", FieldMechanism::Browser),
("hardwareConcurrency", FieldMechanism::Browser),
("screen", FieldMechanism::Browser),
("viewport", FieldMechanism::Browser),
("maxTouchPoints", FieldMechanism::Browser),
("geolocation", FieldMechanism::Browser),
("colorScheme", FieldMechanism::Browser),
("reducedMotion", FieldMechanism::Browser),
("forcedColors", FieldMechanism::Browser),
("platform", FieldMechanism::Browser),
("vendor", FieldMechanism::Script),
("deviceMemory", FieldMechanism::Script),
("doNotTrack", FieldMechanism::Script),
("webgl", FieldMechanism::Script),
];
pub fn fingerprint_field_mechanism(field: &str) -> Option<FieldMechanism> {
FINGERPRINT_FIELD_MECHANISMS
.iter()
.find(|(name, _)| *name == field)
.map(|(_, mechanism)| *mechanism)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fingerprint::profile::{
ColorScheme, FingerprintProfile, ForcedColors, GeolocationProfile, ReducedMotion,
ScreenProfile, ViewportProfile, WebglProfile,
};
const CHROME_UA: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \
(KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36";
#[test]
fn records_a_mechanism_for_every_field_the_profile_accepts() {
let every_field = FingerprintProfile::default()
.user_agent(CHROME_UA)
.accept_language("en-US,en")
.languages(["en-US", "en"])
.locale("en-US")
.timezone_id("UTC")
.platform("Win32")
.vendor("Google Inc.")
.hardware_concurrency(8)
.device_memory(8.0)
.max_touch_points(0)
.do_not_track("1")
.screen(ScreenProfile {
width: Some(1920),
height: Some(1080),
..ScreenProfile::default()
})
.viewport(ViewportProfile {
width: Some(1280),
height: Some(720),
..ViewportProfile::default()
})
.webgl(WebglProfile {
unmasked_vendor: Some("Intel".to_string()),
..WebglProfile::default()
})
.geolocation(GeolocationProfile {
latitude: 0.0,
longitude: 0.0,
accuracy: None,
})
.color_scheme(ColorScheme::Dark)
.reduced_motion(ReducedMotion::Reduce)
.forced_colors(ForcedColors::Active)
.resolve()
.expect("resolves");
assert_eq!(every_field.populated_fields().len(), 19);
for field in every_field.populated_fields() {
assert!(
fingerprint_field_mechanism(field).is_some(),
"no mechanism recorded for {field}"
);
}
}
#[test]
fn mirrors_the_javascript_mechanism_table() {
let script: Vec<&str> = FINGERPRINT_FIELD_MECHANISMS
.iter()
.filter(|(_, mechanism)| *mechanism == FieldMechanism::Script)
.map(|(name, _)| *name)
.collect();
assert_eq!(
script,
vec!["vendor", "deviceMemory", "doNotTrack", "webgl"]
);
assert_eq!(FINGERPRINT_FIELD_MECHANISMS.len(), 19);
}
}