backdisco 0.4.0

Discover backend origins from CDN frontends using LLM-assisted pattern analysis and brute force enumeration
use serde::Deserialize;

#[derive(Debug, Clone, Deserialize)]
pub struct Pattern {
    pub find: String,
    pub replace: String,
    pub position: String, // "prefix", "suffix", "contains"
}

#[derive(Debug, Clone)]
pub struct Candidate {
    pub hostname: String,
}

pub fn apply_patterns(targets: &[String], patterns: &[Pattern]) -> Vec<Candidate> {
    let mut candidates = Vec::new();

    for target in targets {
        for pattern in patterns {
            let candidate = apply_pattern(target, pattern);
            if let Some(c) = candidate {
                candidates.push(Candidate {
                    hostname: c,
                });
            }
        }
    }

    // Deduplicate
    candidates.sort_by(|a, b| a.hostname.cmp(&b.hostname));
    candidates.dedup_by(|a, b| a.hostname == b.hostname);

    candidates
}

fn apply_pattern(target: &str, pattern: &Pattern) -> Option<String> {
    match pattern.position.as_str() {
        "prefix" => {
            if target.starts_with(&pattern.find) {
                Some(target.replacen(&pattern.find, &pattern.replace, 1))
            } else {
                None
            }
        }
        "suffix" => {
            if target.ends_with(&pattern.find) {
                let mut result = target.to_string();
                let start = result.len() - pattern.find.len();
                result.replace_range(start.., &pattern.replace);
                Some(result)
            } else {
                None
            }
        }
        "contains" => {
            if target.contains(&pattern.find) {
                Some(target.replacen(&pattern.find, &pattern.replace, 1))
            } else {
                None
            }
        }
        _ => None,
    }
}