parlov 0.7.0

HTTP oracle detection tool — systematic probing for RFC-compliant information leakage.
Documentation
//! Integration tests for the TRACE existence oracle pipeline.
//!
//! Each test spawns an isolated RFC-compliant TRACE server, drives the adaptive
//! sampling loop via `evaluate`, and asserts the final verdict and severity.

#![deny(clippy::all)]

mod fixtures {
    pub mod trace_server;
}

use fixtures::trace_server::spawn;
use http::{HeaderMap, Method};
use parlov_analysis::existence::ExistenceAnalyzer;
use parlov_analysis::{Analyzer, SampleDecision};
use parlov_core::{
    DifferentialSet, NormativeStrength, OracleClass, OracleResult, OracleVerdict,
    ProbeDefinition, Severity, Technique, Vector,
};
use parlov_probe::http::HttpProbe;
use parlov_probe::Probe;

// --- helpers ---

fn def(url: String) -> ProbeDefinition {
    ProbeDefinition {
        url,
        method: Method::TRACE,
        headers: HeaderMap::new(),
        body: None,
    }
}

fn test_technique() -> Technique {
    Technique {
        id: "test-trace",
        name: "Test TRACE",
        oracle_class: OracleClass::Existence,
        vector: Vector::StatusCodeDiff,
        strength: NormativeStrength::Should,
    }
}

/// Adaptive sampling loop matching the binary's `collect_until_verdict` pattern.
async fn collect_until_verdict(
    route_template: &str,
    baseline_id: &str,
    probe_id: &str,
    addr: std::net::SocketAddr,
) -> OracleResult {
    let client = HttpProbe::new();
    let analyzer = ExistenceAnalyzer;
    let baseline_url = format!("http://{addr}{}", route_template.replace("{id}", baseline_id));
    let probe_url = format!("http://{addr}{}", route_template.replace("{id}", probe_id));
    let mut diff_set = DifferentialSet {
        baseline: Vec::new(),
        probe: Vec::new(),
        technique: test_technique(),
    };

    loop {
        let b = client.execute(&def(baseline_url.clone())).await.expect("baseline request failed");
        let p = client.execute(&def(probe_url.clone())).await.expect("probe request failed");
        diff_set.baseline.push(b);
        diff_set.probe.push(p);

        if let SampleDecision::Complete(result) = analyzer.evaluate(&diff_set) {
            return *result;
        }
    }
}

// --- 200 vs 404 → Confirmed / High ---

#[tokio::test]
async fn trace_200_vs_404_confirmed_high() {
    let addr = spawn().await;
    let result = collect_until_verdict("/resource/{id}", "42", "999", addr).await;
    assert_eq!(result.verdict, OracleVerdict::Confirmed);
    assert_eq!(result.severity, Some(Severity::Medium));
    assert!(result.label.is_some());
    assert!(result.rfc_basis.is_some());
}