parlov 0.8.0

HTTP oracle detection tool — systematic probing for RFC-compliant information leakage.
Documentation
//! Strategy ID filtering for the `scan --strategy` flag.
//!
//! Filters a probe plan down to only the specs whose `metadata.strategy_id`
//! matches one of the requested strategy IDs. Mutually exclusive with
//! `--risk` (non-default) and `--vector`.

use parlov_elicit::ProbeSpec;

/// Extracts the strategy ID from any `ProbeSpec` variant.
pub(crate) fn spec_strategy_id(spec: &ProbeSpec) -> &str {
    match spec {
        ProbeSpec::Pair(p) | ProbeSpec::HeaderDiff(p) => p.metadata.strategy_id,
        ProbeSpec::Burst(b) => b.metadata.strategy_id,
    }
}

/// Keeps only specs whose `strategy_id` is in `ids`.
///
/// An unknown ID produces an empty result — not an error. This mirrors the
/// `--vector` behaviour where an unrecognised vector would also yield nothing.
pub(crate) fn apply_strategy_filters(plan: Vec<ProbeSpec>, ids: &[String]) -> Vec<ProbeSpec> {
    plan.into_iter()
        .filter(|spec| ids.iter().any(|id| id == spec_strategy_id(spec)))
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;
    use http::{HeaderMap, Method};
    use parlov_core::{
        always_applicable, NormativeStrength, OracleClass, ProbeDefinition, SignalSurface,
        Technique, Vector,
    };
    use parlov_elicit::{ProbePair, RiskLevel, StrategyMetadata};

    fn make_def() -> ProbeDefinition {
        ProbeDefinition {
            url: "https://example.com/1".to_owned(),
            method: Method::GET,
            headers: HeaderMap::new(),
            body: None,
        }
    }

    fn make_spec(strategy_id: &'static str) -> ProbeSpec {
        ProbeSpec::Pair(ProbePair {
            baseline: make_def(),
            probe: make_def(),
            canonical_baseline: None,
            metadata: StrategyMetadata {
                strategy_id,
                strategy_name: "Test",
                risk: RiskLevel::Safe,
            },
            technique: Technique {
                id: "test",
                name: "Test",
                oracle_class: OracleClass::Existence,
                vector: Vector::StatusCodeDiff,
                strength: NormativeStrength::Should,
                normalization_weight: Some(0.2),
                inverted_signal_weight: None,
                method_relevant: false,
                parser_relevant: false,
                applicability: always_applicable,
                contradiction_surface: SignalSurface::Status,
            },
            chain_provenance: None,
        })
    }

    #[test]
    fn filter_keeps_matching_spec() {
        let plan = vec![
            make_spec("rd-percent-encoding"),
            make_spec("rd-double-slash"),
        ];
        let result = apply_strategy_filters(plan, &["rd-percent-encoding".to_owned()]);
        assert_eq!(result.len(), 1);
        assert_eq!(spec_strategy_id(&result[0]), "rd-percent-encoding");
    }

    #[test]
    fn filter_unknown_id_yields_empty() {
        let plan = vec![make_spec("rd-percent-encoding")];
        let result = apply_strategy_filters(plan, &["nonexistent-id".to_owned()]);
        assert!(result.is_empty());
    }

    #[test]
    fn filter_multiple_ids_yields_union() {
        let plan = vec![
            make_spec("rd-percent-encoding"),
            make_spec("rd-double-slash"),
            make_spec("cp-if-none-match"),
        ];
        let ids = vec![
            "rd-percent-encoding".to_owned(),
            "rd-double-slash".to_owned(),
        ];
        let result = apply_strategy_filters(plan, &ids);
        assert_eq!(result.len(), 2);
    }

    #[test]
    fn filter_empty_ids_yields_empty() {
        let plan = vec![make_spec("rd-percent-encoding")];
        let result = apply_strategy_filters(plan, &[]);
        assert!(result.is_empty());
    }

    #[test]
    fn filter_empty_plan_yields_empty() {
        let result = apply_strategy_filters(vec![], &["rd-percent-encoding".to_owned()]);
        assert!(result.is_empty());
    }
}