dalfox-rs 0.4.0

Type-safe asynchronous wrapper for the Dalfox XSS scanner (Dalfox ≥3) with JSON findings, stored XSS support, and multi-format result formatting
Documentation
use dalfox_rs::{DalfoxFinding, EventType, Method, Severity};

const V3_VERIFIED_FIXTURE: &str =
    include_str!("fixtures/v3_verified_finding.json");

#[test]
fn test_v3_verified_finding_without_poc_field() {
    let finding: DalfoxFinding =
        serde_json::from_str(V3_VERIFIED_FIXTURE).expect("v3 verified finding JSON");
    assert_eq!(finding.event_type, EventType::Verified);
    assert_eq!(finding.param, "q");
    assert_eq!(finding.severity, Severity::High);
    assert_eq!(finding.poc, "");
    assert!(finding.data.contains("q=%3Csvg"));
    assert_eq!(finding.inject_type.as_deref(), Some("inHTML"));
    assert_eq!(finding.location.as_deref(), Some("Query"));
    assert!(finding.message_str.as_ref().unwrap().contains("Triggered XSS"));
    assert_eq!(
        finding.type_description.as_deref(),
        Some("Verified XSS - payload confirmed executed in parsed DOM")
    );
    assert_eq!(finding.poc_url(), finding.data);
}

#[test]
fn test_v3_envelope_with_verified_finding_fixture() {
    let raw = format!(r#"{{"findings":[{V3_VERIFIED_FIXTURE}],"meta":{{"dalfox_version":"3.1.2"}}}}"#);
    let value: serde_json::Value = serde_json::from_str(&raw).expect("envelope JSON");
    let finding_val = &value["findings"][0];
    let finding: DalfoxFinding =
        serde_json::from_value(finding_val.clone()).expect("finding in envelope");
    assert_eq!(finding.event_type, EventType::Verified);
    assert_eq!(finding.poc_url(), finding.data);
}

#[test]
fn test_v3_json_envelope_empty_findings() {
    let raw = r#"{"findings":[],"meta":{"dalfox_version":"3.1.2","targets_input":1}}"#;
    let value: serde_json::Value = serde_json::from_str(raw).expect("v3 envelope JSON");
    let findings = value["findings"].as_array().expect("findings array");
    assert!(findings.is_empty());
    assert_eq!(value["meta"]["dalfox_version"], "3.1.2");
}

#[test]
fn test_dalfox_json_parsing() {
    let raw_json = r#"{
        "type": "V",
        "poc": "http://example.com/?q=%3Cscript%3Ealert(1)%3C%2Fscript%3E",
        "method": "GET",
        "data": "",
        "param": "q",
        "payload": "\u003cscript\u003ealert(1)\u003c/script\u003e",
        "evidence": "<script>alert(1)</script>",
        "cwe": "CWE-79",
        "severity": "High"
    }"#;

    let finding: DalfoxFinding =
        serde_json::from_str(raw_json).expect("Failed to parse standard Dalfox JSON");

    assert_eq!(finding.event_type, EventType::Verified);
    assert_eq!(finding.method, Method::Get);
    assert_eq!(finding.param, "q");
    assert_eq!(finding.severity, Severity::High);
    assert_eq!(finding.cwe, "CWE-79");
    assert_eq!(finding.payload, "<script>alert(1)</script>");
}

#[test]
fn test_method_and_severity_enums() {
    let method_post: Method = serde_json::from_str("\"POST\"").expect("POST");
    assert_eq!(method_post, Method::Post);

    let method_patch: Method = serde_json::from_str("\"PATCH\"").expect("PATCH");
    assert_eq!(method_patch, Method::Patch);

    let method_other: Method = serde_json::from_str("\"CUSTOM\"").expect("CUSTOM");
    assert_eq!(method_other, Method::Other("CUSTOM".to_string()));

    let sev_info: Severity = serde_json::from_str("\"Information\"").expect("Information");
    assert_eq!(sev_info, Severity::Information);

    let sev_info_alias: Severity = serde_json::from_str("\"Info\"").expect("Info alias");
    assert_eq!(sev_info_alias, Severity::Information);

    let sev_unknown: Severity = serde_json::from_str("\"POTENTIAL\"").expect("unknown severity");
    assert_eq!(sev_unknown, Severity::Unknown("POTENTIAL".to_string()));

    let sev_empty: Severity = serde_json::from_str("\"\"").expect("empty severity");
    assert_eq!(sev_empty, Severity::Unknown(String::new()));
}

#[test]
fn test_event_type_enum() {
    let verified: EventType = serde_json::from_str("\"V\"").expect("Verified");
    assert_eq!(verified, EventType::Verified);

    let grep: EventType = serde_json::from_str("\"G\"").expect("Grep");
    assert_eq!(grep, EventType::Grep);

    let info: EventType = serde_json::from_str("\"I\"").expect("Info");
    assert_eq!(info, EventType::Information);

    let reflected: EventType = serde_json::from_str("\"R\"").expect("Reflected");
    assert_eq!(reflected, EventType::Reflected);

    let ast: EventType = serde_json::from_str("\"A\"").expect("AST");
    assert_eq!(ast, EventType::Ast);

    let unknown: EventType = serde_json::from_str("\"XNEW\"").expect("unknown type");
    assert_eq!(unknown, EventType::Other("XNEW".to_string()));
}

#[test]
fn test_finding_display_uses_data_when_poc_absent() {
    let finding: DalfoxFinding =
        serde_json::from_str(V3_VERIFIED_FIXTURE).expect("v3 finding");
    let display = format!("{finding}");
    assert!(display.contains("Verified"));
    assert!(display.contains("CWE-79"));
    assert!(display.contains(finding.data.as_str()));
}

#[test]
fn test_missing_optional_fields_default() {
    // Dalfox may omit `data` and `evidence` — they should default to empty strings.
    let json = r#"{"type":"G","poc":"http://example.com","method":"GET","param":"q","payload":"test","cwe":"CWE-79","severity":"Low"}"#;
    let finding: DalfoxFinding = serde_json::from_str(json).expect("should parse with defaults");
    assert_eq!(finding.data, "");
    assert_eq!(finding.evidence, "");
    assert_eq!(finding.event_type, EventType::Grep);
}