use regex::{Regex, RegexBuilder};
use std::sync::LazyLock;
static FINGERPRINTS: LazyLock<Vec<(Regex, &'static str)>> = LazyLock::new(|| {
[
(
r#"<meta\s+content=["']blogger["']\s+name=["']generator["']"#,
"fingerprint.blogspot.com",
),
(
r#"<meta\s+name=["']generator["']\s+content=["']blogger["']"#,
"fingerprint.blogspot.com",
),
(
r#"<meta\s+name=["']generator["']\s+content=["']wordpress"#,
"fingerprint.wordpress.com",
),
(
r#"<div\s+class=["']id-SiteBEEPWrap["']\s*>"#,
"fingerprint.ippen.media",
),
]
.into_iter()
.map(|(pattern, host)| {
let regex = RegexBuilder::new(pattern)
.case_insensitive(true)
.build()
.expect("failed to build static regex");
(regex, host)
})
.collect()
});
pub struct Fingerprints;
impl Fingerprints {
pub fn detect(html: &str) -> Option<&'static str> {
FINGERPRINTS
.iter()
.find(|(regex, _)| regex.is_match(html))
.map(|(_, host)| *host)
}
}
#[cfg(test)]
mod tests {
use super::Fingerprints;
#[test]
fn detect() {
assert_eq!(
Fingerprints::detect(r#"<meta name="generator" content="Blogger" />"#),
Some("fingerprint.blogspot.com")
);
assert_eq!(
Fingerprints::detect("<meta content='blogger' name='generator'>"),
Some("fingerprint.blogspot.com")
);
assert_eq!(
Fingerprints::detect(r#"<meta name="generator" content="WordPress 6.5" />"#),
Some("fingerprint.wordpress.com")
);
assert_eq!(
Fingerprints::detect(r#"<body><div class="id-SiteBEEPWrap"><p>x</p></div>"#),
Some("fingerprint.ippen.media")
);
assert_eq!(
Fingerprints::detect(r#"<meta name="generator" content="Hugo 0.120" />"#),
None
);
}
}