use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResponseBaseline {
pub avg_length: usize,
pub headers: HashMap<String, String>,
pub fuzzy_hash: u64,
pub dom_fingerprint: String,
}
impl ResponseBaseline {
pub fn similarity(&self, length: usize, headers: &HashMap<String, String>, fuzzy_hash: u64, dom: &str) -> f64 {
let mut score = 0.0;
let mut weights = 0.0;
weights += 0.4;
if self.dom_fingerprint == dom {
score += 0.4;
}
weights += 0.3;
let mut header_matches = 0;
for (k, v) in &self.headers {
if let Some(val) = headers.get(k) {
if val == v { header_matches += 1; }
}
}
if !self.headers.is_empty() {
score += (header_matches as f64 / self.headers.len() as f64) * 0.3;
}
weights += 0.2;
let len_diff = (self.avg_length as f64 - length as f64).abs();
let len_sim = (1.0 - (len_diff / self.avg_length.max(1) as f64)).max(0.0);
score += len_sim * 0.2;
weights += 0.1;
if self.fuzzy_hash == fuzzy_hash {
score += 0.1;
}
score / weights
}
pub fn is_mirror(&self, length: usize, headers: &HashMap<String, String>, fuzzy_hash: u64, dom: &str) -> bool {
self.similarity(length, headers, fuzzy_hash, dom) > 0.85
}
}
pub fn generate_dom_fingerprint(html: &str) -> String {
let mut tags = Vec::new();
let mut in_tag = false;
let mut current_tag = String::new();
for c in html.chars() {
if c == '<' {
in_tag = true;
current_tag.clear();
} else if c == '>' || c == ' ' || c == '/' {
if in_tag && !current_tag.is_empty() {
tags.push(current_tag.to_lowercase());
}
in_tag = false;
} else if in_tag {
current_tag.push(c);
}
}
tags.join(",")
}
pub fn calculate_fuzzy_hash(data: &str) -> u64 {
use hashkit::wyhash;
let mut hash = 0u64;
for chunk in data.as_bytes().chunks(64) {
hash ^= wyhash::hash(chunk, 0);
}
hash
}