#![cfg(windows)]
//! Advanced ADS exploitation tests
//!
//! This test suite covers sophisticated ADS attack vectors including type token confusion,
//! chaining attacks, and filesystem limit exploitation.

use soft_canonicalize::soft_canonicalize;
use std::fs;
use std::io::{self, ErrorKind};

fn expect_invalid(res: io::Result<impl std::fmt::Debug>, pattern: &str) {
    match res {
        Ok(v) => panic!("Expected InvalidInput for pattern '{pattern}', got Ok({v:?})"),
        Err(e) => assert_eq!(
            e.kind(),
            ErrorKind::InvalidInput,
            "Expected InvalidInput for pattern '{pattern}', got {e:?}"
        ),
    }
}

fn expect_ok(res: io::Result<impl std::fmt::Debug>, pattern: &str) {
    match res {
        Ok(_) => {}
        Err(e) => panic!("Expected Ok for pattern '{pattern}', got Err({e:?})"),
    }
}

#[test]
fn test_ads_type_token_confusion_attacks() -> io::Result<()> {
    let tmp = tempfile::tempdir()?;
    let base = tmp.path().join("test.txt");
    fs::write(&base, b"test")?;

    // Type token confusion attacks - malformed type tokens that could bypass validation
    let type_confusion_attacks = [
        // Double dollar signs
        "test.txt:stream:$$DATA",
        "test.txt:stream:$DATA$",
        "test.txt:stream:$DATA$EXTRA",
        // Case mixing with traversal
        "test.txt:stream:$dAtA..\\evil.exe",
        "test.txt:stream:$Data..\\bypass.exe",
        "test.txt:stream:$data..\\exploit.exe",
        // Invalid type token formats
        "test.txt:stream:DATA$",
        "test.txt:stream:$",
        "test.txt:stream:$ DATA", // space after $
        "test.txt:stream:$DATA ", // space after DATA
        "test.txt:stream: $DATA", // space before $
        // Multiple type tokens
        "test.txt:stream:$DATA:$BITMAP",
        "test.txt:stream:$DATA:$DATA",
        "test.txt:stream:$BITMAP:$DATA..\\evil.exe",
        // Type token with traversal injection
        "test.txt:stream:$DATA..\\..\\evil.exe",
        "test.txt:stream:$BITMAP..\\exploit.exe",
        "test.txt:stream:$INDEX_ROOT..\\bypass.exe",
        // Mixed separators in type tokens
        "test.txt:stream:$DATA/evil.exe",
        "test.txt:stream:$DATA\\evil.exe",
        // Null bytes in type tokens
        "test.txt:stream:$DATA\0",
        "test.txt:stream:$DATA\0..\\evil.exe",
        // Unicode in type tokens
        "test.txt:stream:$DATÄ",
        "test.txt:stream:$DATA\u{200B}", // zero-width space
        // Overlong type tokens - these should be valid since lib.rs allows long alphanumeric type tokens
        // &format!("test.txt:stream:${}", "DATA".repeat(10)), // Remove - actually valid
        // &format!("test.txt:stream:${}..\\evil.exe", "BITMAP".repeat(10)), // Keep - has traversal
        &format!("test.txt:stream:${}..\\evil.exe", "BITMAP".repeat(10)), // with traversal
    ];

    for pattern in type_confusion_attacks {
        let path = tmp.path().join(pattern);
        expect_invalid(soft_canonicalize(&path), pattern);
    }

    Ok(())
}

#[test]
fn test_ads_chaining_attacks() -> io::Result<()> {
    let tmp = tempfile::tempdir()?;
    let base = tmp.path().join("test.txt");
    fs::write(&base, b"test")?;

    // ADS chaining attacks - multiple colons and nested streams
    let chaining_attacks = [
        // Triple colon attacks
        "test.txt:stream1:stream2:..\\evil.exe",
        "file.txt:stream1:stream2:$DATA:..\\evil.exe",
        "test.txt:a:b:c:..\\exploit.exe",
        "test.txt:legitimate:nested:..\\bypass.exe",
        // Chaining with type tokens
        "test.txt:stream1:$DATA:stream2:..\\evil.exe",
        "test.txt:a:$BITMAP:b:..\\exploit.exe",
        "test.txt:stream:$DATA:..\\evil.exe:$MORE",
        // Deep nesting
        "test.txt:a:b:c:d:e:f:g:h:i:j:..\\evil.exe",
        "test.txt:1:2:3:4:5:6:7:8:9:10:..\\exploit.exe",
        // Mixed content in chains
        "test.txt:stream:$DATA:MORE:..\\evil.exe",
        "test.txt:a:$BITMAP:nested:$DATA:..\\bypass.exe",
        // Chaining with directory separators
        "test.txt:stream1:stream2\\..\\evil.exe",
        "test.txt:a:b/..\\exploit.exe",
        "test.txt:chain:item\\subdir\\..\\bypass.exe",
        // Unicode in chains
        "test.txt:strëam:nësted:..\\evil.exe",
        "test.txt:文档:stream:..\\exploit.exe",
        // Empty components in chains
        "test.txt::stream:..\\evil.exe",
        "test.txt:stream::..\\exploit.exe",
        "test.txt:a::b:..\\bypass.exe",
        // Whitespace in chains
        "test.txt:stream :nested:..\\evil.exe",
        "test.txt:a: b:..\\exploit.exe",
        "test.txt: :stream:..\\bypass.exe",
        // Very long chains
        &format!(
            "test.txt:{}:..\\evil.exe",
            (0..50)
                .map(|i| format!("chain{i}"))
                .collect::<Vec<_>>()
                .join(":")
        ),
    ];

    for pattern in chaining_attacks {
        let path = tmp.path().join(pattern);
        expect_invalid(soft_canonicalize(&path), pattern);
    }

    Ok(())
}

#[test]
fn test_ads_8_3_filename_bypass() -> io::Result<()> {
    let tmp = tempfile::tempdir()?;
    let base = tmp.path().join("PROGRA~1.TXT");
    fs::write(&base, b"test")?;

    // 8.3 filename + ADS combination attacks
    let filename_8_3_attacks = [
        // Standard 8.3 + ADS traversal
        "PROGRA~1.TXT:..\\..\\evil.exe",
        "MICROS~1.TXT:stream..\\bypass.exe",
        "WINDOW~1.TXT:hidden..\\exploit.exe",
        // 8.3 with type tokens
        "PROGRA~1.TXT:stream:$DATA..\\evil.exe",
        "MICROS~1.TXT:hidden:$BITMAP..\\bypass.exe",
        // Mixed case 8.3
        "progra~1.txt:..\\evil.exe",
        "PROGRA~1.txt:stream..\\bypass.exe",
        "Progra~1.Txt:hidden..\\exploit.exe",
        // Multiple tilde numbers
        "PROGRA~2.TXT:..\\evil.exe",
        "PROGRA~9.TXT:stream..\\bypass.exe",
        "PROGRA~10.TXT:hidden..\\exploit.exe",
        // 8.3 edge cases
        "A~1.TXT:..\\evil.exe",
        "VERYLON~1.TXT:stream..\\bypass.exe",
        "~1.TXT:hidden..\\exploit.exe",
        // Invalid 8.3 patterns
        "PROGRA~~1.TXT:..\\evil.exe",
        "PROGRA~.TXT:stream..\\bypass.exe",
        "PROGRA~1~2.TXT:hidden..\\exploit.exe",
        // 8.3 with Unicode
        "PROGRÄ~1.TXT:..\\evil.exe",
        "MICROS~1.TXT:strëam..\\bypass.exe",
        "PROGRA~1.TXT:..\\..\\evil.exe:$DATA", // explicit traversal + type token variant
    ];

    for pattern in filename_8_3_attacks {
        let path = tmp.path().join(pattern);
        expect_invalid(soft_canonicalize(&path), pattern);
    }

    Ok(())
}

#[test]
fn test_ads_reserved_names_bypass() -> io::Result<()> {
    let tmp = tempfile::tempdir()?;

    // Reserved names + ADS combination attacks
    let reserved_name_attacks = [
        // Standard reserved names with traversal
        "CON.txt:stream:..\\evil.exe",
        "PRN.txt:hidden:..\\bypass.exe",
        "AUX.txt:data:..\\exploit.exe",
        "NUL.txt:stream:..\\evil.exe",
        "COM1.txt:hidden:..\\bypass.exe",
        "COM9.txt:data:..\\exploit.exe",
        "LPT1.txt:stream:..\\evil.exe",
        "LPT9.txt:hidden:..\\bypass.exe",
        // Reserved names without extension
        "CON:stream:..\\evil.exe",
        "PRN:hidden:..\\bypass.exe",
        "AUX:data:..\\exploit.exe",
        "NUL:stream:..\\evil.exe",
        // Mixed case reserved names
        "con.txt:stream:..\\evil.exe",
        "Con.Txt:hidden:..\\bypass.exe",
        "CON.TXT:data:..\\exploit.exe",
        // Reserved names with type tokens
        "CON.txt:stream:$DATA..\\evil.exe",
        "PRN.txt:hidden:$BITMAP..\\bypass.exe",
        // Reserved names in stream names
        "test.txt:CON:..\\evil.exe",
        "test.txt:PRN..\\bypass.exe",
        "test.txt:AUX:$DATA..\\exploit.exe",
        // Multiple reserved components
        "CON.txt:PRN:..\\evil.exe",
        "AUX.txt:COM1..\\bypass.exe",
        // Reserved names with spaces
        "CON .txt:stream:..\\evil.exe",
        " PRN.txt:hidden:..\\bypass.exe",
        "AUX .txt:data:..\\exploit.exe",
    ];

    for pattern in reserved_name_attacks {
        let path = tmp.path().join(pattern);
        expect_invalid(soft_canonicalize(&path), pattern);
    }

    Ok(())
}

#[test]
fn test_ads_length_limit_exploitation() -> io::Result<()> {
    let tmp = tempfile::tempdir()?;
    let base = tmp.path().join("test.txt");
    fs::write(&base, b"test")?;

    // Test exactly at and beyond Windows length limits

    // Windows stream name limit is typically 255 characters
    // Separate clearly valid boundary cases from invalid exploitation attempts.

    let valid_boundary = [
        format!("test.txt:{}", "A".repeat(255)), // exactly at limit
        format!("test.txt:{}:$DATA", "B".repeat(249)), // stream part 249 + 6 chars ":$DATA" = 255
        format!("test.txt:{}", "ValidLong".repeat(25)), // 25*9 = 225 chars < 255
        format!("test.txt:{}:$DATA", "Stream".repeat(35)), // 35*6 = 210 + 6 = 216 chars < 255
    ];
    for pattern in &valid_boundary {
        let path = tmp.path().join(pattern);
        expect_ok(soft_canonicalize(&path), pattern);
    }

    let invalid_length_attacks = [
        format!("test.txt:{}", "C".repeat(256)), // just over
        format!("test.txt:{}", "D".repeat(300)), // well over
        format!("test.txt:{}..\\evil.exe", "E".repeat(245)), // traversal appended
        format!("test.txt:{}..\\..\\bypass.exe", "F".repeat(240)),
        format!("test.txt:{}:$DATA..\\evil.exe", "G".repeat(240)),
        format!("test.txt:{}:$BITMAP..\\bypass.exe", "H".repeat(235)),
    ];
    for pattern in &invalid_length_attacks {
        let path = tmp.path().join(pattern);
        expect_invalid(soft_canonicalize(&path), pattern);
    }

    Ok(())
}

#[test]
fn test_ads_unicode_length_attacks() -> io::Result<()> {
    let tmp = tempfile::tempdir()?;
    let base = tmp.path().join("test.txt");
    fs::write(&base, b"test")?;

    // Unicode characters that might bypass length calculations
    let unicode_attacks = [
        // Multi-byte UTF-8 characters
        &format!("test.txt:{}..\\evil.exe", "ñ".repeat(200)), // Each ñ is 2 bytes in UTF-8
        &format!("test.txt:{}..\\bypass.exe", "文".repeat(100)), // Each 文 is 3 bytes in UTF-8
        &format!("test.txt:{}..\\exploit.exe", "𝕏".repeat(50)), // Each 𝕏 is 4 bytes in UTF-8
        // Combining characters
        &format!("test.txt:{}..\\evil.exe", "a\u{0301}".repeat(100)), // a with combining acute
        &format!("test.txt:{}..\\bypass.exe", "e\u{0300}\u{0301}".repeat(50)), // e with multiple combiners
        // Zero-width characters
        &format!("test.txt:{}..\\evil.exe", "a\u{200B}".repeat(200)), // zero-width space
        &format!("test.txt:{}..\\bypass.exe", "b\u{FEFF}".repeat(100)), // BOM
        // Mixed width characters
        &format!("test.txt:a{}z..\\evil.exe", "文".repeat(100)),
        &format!("test.txt:{}..\\bypass.exe", "a文b𝕏c".repeat(50)),
    ];

    for pattern in unicode_attacks {
        let path = tmp.path().join(pattern);
        expect_invalid(soft_canonicalize(&path), pattern);
    }

    Ok(())
}

#[test]
fn test_ads_nested_filesystem_attacks() -> io::Result<()> {
    let tmp = tempfile::tempdir()?;
    let base = tmp.path().join("test.txt");
    fs::write(&base, b"test")?;

    // Attacks targeting filesystem-level ADS handling
    let filesystem_attacks = [
        // Case folding attacks
        "TEST.TXT:stream:..\\evil.exe",    // uppercase filename
        "test.TXT:STREAM:..\\bypass.exe",  // mixed case
        "Test.Txt:Stream:..\\exploit.exe", // title case
        // Normalization attacks
        "test.txt:café:..\\evil.exe",           // é as single character
        "test.txt:cafe\u{0301}:..\\bypass.exe", // é as e + combining acute
        // Filesystem metadata injection
        "test.txt:stream:$MFT..\\evil.exe",        // NTFS metadata
        "test.txt:stream:$BOOT..\\bypass.exe",     // NTFS boot sector
        "test.txt:stream:$BadClus..\\exploit.exe", // NTFS bad clusters
        // Reparse point confusion
        "test.txt:stream:REPARSE..\\evil.exe",
        "test.txt:$REPARSE_POINT:..\\bypass.exe",
        // Volume information attacks
        "test.txt:$VOLUME_INFORMATION:..\\evil.exe",
        "test.txt:$RECYCLE.BIN:..\\bypass.exe",
        // System file attacks
        "test.txt:hiberfil.sys:..\\evil.exe",
        "test.txt:pagefile.sys:..\\bypass.exe",
        "test.txt:swapfile.sys:..\\exploit.exe",
    ];

    for pattern in filesystem_attacks {
        let path = tmp.path().join(pattern);
        expect_invalid(soft_canonicalize(&path), pattern);
    }

    Ok(())
}
