zenith-http1 0.1.0

Zenith HTTP/1.1 协议解析器(RFC 7230):零堆分配热路径、流式解析、CRLF 防注入
Documentation
//! HTTP/1.1 类型定义

use std::fmt;

use smallvec::SmallVec;

/// 单个已解析头部:名称(小写规范化)与值,均为 `Box<str>`
/// (§6.1.1:替代 `String` 节省 8 字节容量字段,无后续增长需求)。
pub type HeaderEntry = (Box<str>, Box<str>);

/// 已解析头部列表:`SmallVec` 栈内 16 条目(§6.1.1 热路径零堆分配:
/// 绝大多数请求头部数 ≤16 时整个列表无堆分配)。
pub type HeaderList = SmallVec<[HeaderEntry; 16]>;

/// HTTP/1.1 解析器配置
#[derive(Debug, Clone)]
pub struct Http1Config {
    /// 最大请求行大小(字节)
    pub max_request_line_size: usize,
    /// 最大请求头大小(字节)
    pub max_header_size: usize,
    /// 最大请求头数量
    pub max_header_count: usize,
    /// 最大请求体大小(字节)
    pub max_body_size: usize,
    /// 是否启用 keep-alive
    pub keep_alive: bool,
    /// 每个头部名称最大长度
    pub max_header_name_len: usize,
    /// 每个头部值最大长度
    pub max_header_value_len: usize,
    /// 请求空闲超时(毫秒),防止 Slowloris 慢速攻击
    pub idle_timeout_ms: u64,
    /// 解析器内部缓冲区最大容量(字节),累积未完成请求的上限
    pub max_buffer_size: usize,
}

impl Http1Config {
    /// 创建新的 HTTP/1.1 配置
    #[inline]
    pub fn new() -> Self {
        Self {
            max_request_line_size: 8192,
            max_header_size: 8192,
            max_header_count: 256,
            max_body_size: 1_048_576,
            keep_alive: true,
            max_header_name_len: 64,
            max_header_value_len: 8192,
            idle_timeout_ms: 30_000,
            max_buffer_size: 65_536,
        }
    }

    /// 设置最大头部大小
    #[inline]
    pub fn with_max_header_size(mut self, size: usize) -> Self {
        self.max_header_size = size;
        self
    }

    /// 设置最大请求体大小
    #[inline]
    pub fn with_max_body_size(mut self, size: usize) -> Self {
        self.max_body_size = size;
        self
    }

    /// 设置最大头部数量
    #[inline]
    pub fn with_max_header_count(mut self, n: usize) -> Self {
        self.max_header_count = n;
        self
    }

    /// 设置空闲超时(毫秒),防止 Slowloris 慢速攻击
    #[inline]
    pub fn with_idle_timeout_ms(mut self, ms: u64) -> Self {
        self.idle_timeout_ms = ms;
        self
    }

    /// 设置解析器缓冲区最大容量(字节)
    #[inline]
    pub fn with_max_buffer_size(mut self, size: usize) -> Self {
        self.max_buffer_size = size;
        self
    }
}

impl Default for Http1Config {
    fn default() -> Self {
        Self::new()
    }
}

/// HTTP/1.1 错误类型
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Http1Error {
    /// 语法错误
    SyntaxError(String),
    /// 头部过大
    HeaderTooLarge,
    /// 请求行过长(M-3:完整行与未完成行统一语义)
    RequestLineTooLong,
    /// 头部过多
    TooManyHeaders,
    /// 请求体过大
    BodyTooLarge,
    /// 缺少 Host 头
    MissingHost,
    /// 方法不支持
    UnsupportedMethod(String),
    /// 版本不支持
    UnsupportedVersion(String),
    /// 请求走私检测
    SmugglingDetected(String),
    /// 分块编码错误
    ChunkedError(String),
    /// 协议不一致(Content-Length / Transfer-Encoding 共存等)
    ProtocolInconsistency(String),
    /// 连接已关闭
    ConnectionClosed,
    /// 数据不足
    NeedMoreData,
    /// 空闲超时(Slowloris 防护)
    IdleTimeout,
    /// 缓冲区溢出(累积数据超过上限)
    BufferOverflow,
    /// 内部错误
    Internal(String),
}

impl fmt::Display for Http1Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::SyntaxError(m) => write!(f, "HTTP 语法错误: {m}"),
            Self::HeaderTooLarge => write!(f, "HTTP 头部过大"),
            Self::RequestLineTooLong => write!(f, "HTTP 请求行过长"),
            Self::TooManyHeaders => write!(f, "HTTP 头部数量超限"),
            Self::BodyTooLarge => write!(f, "HTTP 请求体过大"),
            Self::MissingHost => write!(f, "HTTP 缺少 Host 头"),
            Self::UnsupportedMethod(m) => write!(f, "HTTP 方法不支持: {m}"),
            Self::UnsupportedVersion(v) => write!(f, "HTTP 版本不支持: {v}"),
            Self::SmugglingDetected(m) => write!(f, "HTTP 请求走私: {m}"),
            Self::ChunkedError(m) => write!(f, "HTTP 分块编码错误: {m}"),
            Self::ProtocolInconsistency(m) => write!(f, "HTTP 协议不一致: {m}"),
            Self::ConnectionClosed => write!(f, "HTTP 连接已关闭"),
            Self::NeedMoreData => write!(f, "HTTP 数据不足"),
            Self::IdleTimeout => write!(f, "HTTP 空闲超时 (Slowloris 防护)"),
            Self::BufferOverflow => write!(f, "HTTP 缓冲区溢出"),
            Self::Internal(m) => write!(f, "HTTP 内部错误: {m}"),
        }
    }
}

impl std::error::Error for Http1Error {}

/// HTTP/1.1 请求行
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HttpRequestLine {
    /// 方法(原始大写)
    pub method: Box<str>,
    /// 请求目标
    pub target: Box<str>,
    /// HTTP 版本
    pub version: Box<str>,
}

/// HTTP/1.1 请求
#[derive(Debug, Clone)]
pub struct HttpRequest {
    /// 请求行
    pub line: HttpRequestLine,
    /// 请求头(名称已规范化为小写)
    pub headers: HeaderList,
    /// 请求体(未解析原始字节)
    pub body: Vec<u8>,
    /// 长度已确认(Content-Length)
    pub content_length: Option<u64>,
    /// 是否 chunked 编码
    pub chunked: bool,
    /// 是否为 keep-alive
    pub keep_alive: bool,
}

impl HttpRequest {
    /// 创建新请求
    ///
    /// keep_alive 默认值按 RFC 7230 §6.3 / RFC 1945 §1.3:
    /// - HTTP/1.0 默认关闭连接(仅显式 `Connection: keep-alive` 才保持)
    /// - HTTP/1.1 默认保持(仅显式 `Connection: close` 才关闭)
    #[inline]
    pub fn new(method: Box<str>, target: Box<str>, version: Box<str>) -> Self {
        let keep_alive = &*version != "HTTP/1.0";
        Self {
            line: HttpRequestLine {
                method,
                target,
                version,
            },
            headers: SmallVec::new(),
            body: Vec::new(),
            content_length: None,
            chunked: false,
            keep_alive,
        }
    }

    /// 查找头部值(按规范化小写名称)
    #[inline]
    pub fn get_header<'a>(&'a self, name: &str) -> Option<&'a str> {
        let name = name.to_ascii_lowercase();
        self.headers
            .iter()
            .find(|(k, _)| k.as_ref() == name.as_str())
            .map(|(_, v)| v.as_ref())
    }

    /// 获取 Host
    #[inline]
    pub fn host(&self) -> Option<&str> {
        self.get_header("host")
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_config_default() {
        let c = Http1Config::new();
        assert_eq!(c.max_header_size, 8192);
        assert_eq!(c.max_body_size, 1_048_576);
        assert_eq!(c.max_header_count, 256);
        assert!(c.keep_alive);
    }

    #[test]
    fn test_config_custom() {
        let c = Http1Config::new()
            .with_max_header_size(4096)
            .with_max_body_size(524_288)
            .with_max_header_count(128);
        assert_eq!(c.max_header_size, 4096);
        assert_eq!(c.max_body_size, 524_288);
        assert_eq!(c.max_header_count, 128);
    }

    #[test]
    fn test_error_display() {
        assert_eq!(
            Http1Error::MissingHost.to_string(),
            "HTTP 缺少 Host 头"
        );
        assert!(matches!(Http1Error::SmugglingDetected("CL.TE".into()), Http1Error::SmugglingDetected(_)));
    }

    #[test]
    fn test_request_get_header() {
        let mut req = HttpRequest::new("GET".into(), "/".into(), "HTTP/1.1".into());
        req.headers.push(("host".into(), "example.com".into()));
        req.headers.push(("content-length".into(), "0".into()));
        assert_eq!(req.get_header("host"), Some("example.com"));
        assert_eq!(req.get_header("Content-Length"), Some("0"));
        assert_eq!(req.get_header("missing"), None);
    }

    #[test]
    fn test_http1_error_all_variants_display() {
        let errors = vec![
            Http1Error::SyntaxError("test".into()),
            Http1Error::HeaderTooLarge,
            Http1Error::RequestLineTooLong,
            Http1Error::TooManyHeaders,
            Http1Error::BodyTooLarge,
            Http1Error::MissingHost,
            Http1Error::UnsupportedMethod("FOO".into()),
            Http1Error::UnsupportedVersion("HTTP/3.0".into()),
            Http1Error::SmugglingDetected("test".into()),
            Http1Error::ChunkedError("test".into()),
            Http1Error::ProtocolInconsistency("test".into()),
            Http1Error::ConnectionClosed,
            Http1Error::NeedMoreData,
            Http1Error::Internal("test".into()),
        ];
        for e in errors {
            let s = e.to_string();
            assert!(!s.is_empty());
        }
    }

    #[test]
    fn test_http_request_new() {
        let req = HttpRequest::new("POST".into(), "/api".into(), "HTTP/1.1".into());
        assert_eq!(req.line.method.as_ref(), "POST");
        assert_eq!(req.line.target.as_ref(), "/api");
        assert_eq!(req.line.version.as_ref(), "HTTP/1.1");
        assert!(req.headers.is_empty());
        assert!(req.body.is_empty());
        assert_eq!(req.content_length, None);
        assert!(!req.chunked);
        assert!(req.keep_alive);
    }

    #[test]
    fn test_http_request_new_http10_default_close() {
        // RFC 1945:HTTP/1.0 默认关闭连接
        let req = HttpRequest::new("GET".into(), "/".into(), "HTTP/1.0".into());
        assert!(!req.keep_alive, "HTTP/1.0 默认 keep_alive 必须为 false");
    }

    #[test]
    fn test_http_request_host() {
        let mut req = HttpRequest::new("GET".into(), "/".into(), "HTTP/1.1".into());
        assert_eq!(req.host(), None);
        req.headers.push(("host".into(), "example.com".into()));
        assert_eq!(req.host(), Some("example.com"));
    }

    #[test]
    fn test_http_request_line_clone() {
        let line = HttpRequestLine {
            method: "GET".into(),
            target: "/".into(),
            version: "HTTP/1.1".into(),
        };
        let line2 = line.clone();
        assert_eq!(line, line2);
        assert_eq!(format!("{:?}", line), format!("{:?}", line2));
    }

    #[test]
    fn test_config_default_trait() {
        let c1 = Http1Config::new();
        let c2 = Http1Config::default();
        assert_eq!(c1.max_header_size, c2.max_header_size);
        assert_eq!(c1.max_body_size, c2.max_body_size);
    }

    #[test]
    fn test_error_debug_and_clone() {
        let e = Http1Error::SyntaxError("test".into());
        let e2 = e.clone();
        assert_eq!(e, e2);
        let _ = format!("{:?}", e);
    }
}