zenith-api 0.1.0

Zenith 公共 API 与类型定义:CanonicalRequest/Response、Method、Protocol、Transport 等核心类型
Documentation
//! 跨层百分号解码一致性测试
//!
//! 验证 WAF 解码路径与 extractor 解码路径使用同一套统一解码语义,
//! 消除"同一输入各层解码结果不同"的协议差分攻击面:
//! - WAF 路径(zenith-waf):`percent_decode_bytes_into`(字节级 Preserve,
//!   `+` 转空格)+ UTF-8 校验
//! - extractor 路径(zenith-web):`percent_decode_with_policy`
//!   (字符串级 Preserve,`+` 转空格)
//!
//! 两条路径均委托本 crate 的统一实现,对任意输入(含对抗用例)
//! 解码结果必须完全一致。

use zenith_api::normalize::{
    percent_decode_bytes_into, percent_decode_with_policy, InvalidSequencePolicy,
};

/// 模拟 WAF 解码路径(zenith-waf::percent_decode_into 的完整语义):
/// 字节级 Preserve 解码写入栈缓冲区,再做 UTF-8 校验
fn waf_decode_path(input: &str) -> Option<String> {
    let mut buf = [0u8; 4096];
    let decoded = percent_decode_bytes_into(input.as_bytes(), &mut buf, true)?;
    std::str::from_utf8(decoded).ok().map(str::to_string)
}

/// 模拟 extractor 解码路径(zenith-web::url_decode 的完整语义):
/// 字符串级 Preserve 解码,`+` 转空格
fn extractor_decode_path(input: &str) -> Option<String> {
    percent_decode_with_policy(input, true, InvalidSequencePolicy::Preserve)
}

/// 对抗用例集:覆盖非法序列、表单语义、非法 UTF-8、双重编码、路径遍历
const ADVERSARIAL_CASES: &[&str] = &[
    // 非法 % 序列(Preserve:按原字节保留)
    "%ZZ",
    "test%ZZdata",
    "%",
    "%2",
    "100%+pure",
    "%2Z%41",
    // 表单语义(+ 转空格)
    "a+b",
    "a+b+c",
    "name=John+Doe",
    // 非法 UTF-8(两条路径均须拒绝)
    "%FF",
    "%FF%FE",
    "bad=%FF&good=ok",
    // 合法编码
    "%41%42%43",
    "%2f%2F",
    "%E4%BD%A0%E5%A5%BD",
    "hello%20world",
    // 路径遍历 / 双重编码攻击形态
    "%2e%2e%2f",
    "%252e%252e%252f",
    "..%2f..%2fetc%2fpasswd",
    // 大小写混合十六进制:两条路径均须按大小写不敏感解码
    "%2E%2E%2F",
    "%2e%2F%2E",
    // 混合表单语义与编码序列
    "%41+%42",
    "space%20%20here",
    // 双重编码百分号:单层解码产出 %25 字面
    "%2525",
    // SQL 注入攻击形态
    "id=%27+OR+%271%27%3D%271",
    "id=1'+OR+'1'='1+%ZZ",
    // 边界
    "",
    "plain",
    "%20",
];

#[test]
fn test_waf_extractor_decode_consistency() {
    for case in ADVERSARIAL_CASES {
        let waf = waf_decode_path(case);
        let extractor = extractor_decode_path(case);
        assert_eq!(
            waf, extractor,
            "WAF 与 extractor 解码结果不一致(协议差分): {:?}",
            case
        );
    }
}

#[test]
fn test_consistency_spot_values() {
    // 逐条锚定关键用例的具体解码结果,防止语义漂移
    assert_eq!(waf_decode_path("%ZZ"), Some("%ZZ".to_string()));
    assert_eq!(extractor_decode_path("%ZZ"), Some("%ZZ".to_string()));

    assert_eq!(waf_decode_path("a+b"), Some("a b".to_string()));
    assert_eq!(extractor_decode_path("a+b"), Some("a b".to_string()));

    // 非法 UTF-8:两条路径均 fail-closed 返回 None
    assert_eq!(waf_decode_path("%FF"), None);
    assert_eq!(extractor_decode_path("%FF"), None);

    // 路径遍历编码:两条路径均解码为 ../
    assert_eq!(waf_decode_path("%2e%2e%2f"), Some("../".to_string()));
    assert_eq!(extractor_decode_path("%2e%2e%2f"), Some("../".to_string()));

    // 双重编码:单次解码结果为 %2e%2e%2f(两层各自再解码由各自逻辑负责)
    assert_eq!(
        waf_decode_path("%252e%252e%252f"),
        Some("%2e%2e%2f".to_string())
    );
    assert_eq!(
        extractor_decode_path("%252e%252e%252f"),
        Some("%2e%2e%2f".to_string())
    );
}