parlov 0.8.0

HTTP oracle detection tool — systematic probing for RFC-compliant information leakage.
Documentation
//! `ScanContext` construction from parsed `ScanArgs`.
//!
//! CLI string parsers live in `parse.rs`.

use parlov_core::Error;
use parlov_elicit::ScanContext;

use crate::parse::{parse_alt_credential, parse_known_duplicate, parse_risk, parse_state_field};
use crate::util::parse_headers;
use crate::vector_filter::{max_risk_from_filters, VectorFilter};

/// Builds a `ScanContext` from `ScanArgs`. When `--vector` flags are present,
/// sets `max_risk` to the highest per-vector ceiling so `generate_plan` produces
/// a superset that is then post-filtered.
pub(crate) fn build_scan_context(
    args: &crate::cli::ScanArgs,
    vector_filters: &[VectorFilter],
) -> Result<ScanContext, Error> {
    let probe_id = args
        .probe_id
        .clone()
        .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());

    let max_risk = if vector_filters.is_empty() {
        parse_risk(&args.risk)?
    } else {
        max_risk_from_filters(vector_filters)
    };
    let headers = parse_headers(&args.headers)?;

    let known_duplicate = args
        .known_duplicate
        .as_deref()
        .map(parse_known_duplicate)
        .transpose()?;

    let state_field = args
        .state_field
        .as_deref()
        .map(parse_state_field)
        .transpose()?;

    let alt_credential = args
        .alt_credential
        .as_deref()
        .map(parse_alt_credential)
        .transpose()?;

    Ok(ScanContext {
        target: args.target.clone(),
        baseline_id: args.baseline_id.clone(),
        probe_id,
        headers,
        max_risk,
        known_duplicate,
        state_field,
        alt_credential,
        body_template: args.body.clone(),
    })
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::cli::ScanArgs;
    use parlov_elicit::RiskLevel;

    fn minimal_args(target: &str, baseline_id: &str) -> ScanArgs {
        ScanArgs {
            target: target.to_owned(),
            baseline_id: baseline_id.to_owned(),
            probe_id: Some("9999".to_owned()),
            risk: "safe".to_owned(),
            headers: vec![],
            alt_credential: None,
            known_duplicate: None,
            state_field: None,
            vectors: vec![],
            strategies: vec![],
            body: None,
            exhaustive: false,
            repro: false,
            verbose: false,
        }
    }

    #[test]
    fn build_scan_context_target_matches_and_risk_defaults_to_safe() {
        let args = minimal_args("https://api.example.com/users/{id}", "1001");
        let ctx = build_scan_context(&args, &[]).unwrap();
        assert_eq!(ctx.target, "https://api.example.com/users/{id}");
        assert_eq!(ctx.max_risk, RiskLevel::Safe);
    }
}